import { describe, it, expect, vi, afterEach, beforeAll, afterAll, beforeEach } from 'vitest'
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { http, HttpResponse } from 'msw'
import { setupServer } from 'msw/node'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import React from 'react'
import { ChargeableCharFormDialog } from '../components/ChargeableCharFormDialog'
import type { ChargeableCharConfig } from '../types'

vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn() } }))

// Mock ProductTypeSelect so it renders a simple select with a "Global" option
// This avoids fetching product-types in form dialog tests
vi.mock('../components/ProductTypeSelect', () => ({
  ProductTypeSelect: ({ value, onValueChange, disabled }: {
    value: number | null | undefined
    onValueChange: (v: number | null | undefined) => void
    disabled?: boolean
  }) => (
    <select
      aria-label="Tipo de producto"
      value={value === null ? '__global__' : value === undefined ? '__all__' : String(value)}
      onChange={(e) => {
        const v = e.target.value
        onValueChange(v === '__global__' ? null : v === '__all__' ? undefined : Number(v))
      }}
      disabled={disabled}
    >
      <option value="__global__">Global (todos los tipos)</option>
      <option value="1">Clasificados</option>
      <option value="2">Notables</option>
    </select>
  ),
}))

const API_URL = 'http://localhost:5000'

const server = setupServer()

beforeAll(() => server.listen({ onUnhandledRequest: 'warn' }))
afterEach(() => { server.resetHandlers(); vi.clearAllMocks(); vi.useRealTimers() })
afterAll(() => server.close())

function setupFakeTimers() {
  // Fix today to 2026-04-20 ART
  // 2026-04-20T15:00:00-03:00 = 2026-04-20T18:00:00Z
  vi.useFakeTimers({ shouldAdvanceTime: true })
  vi.setSystemTime(new Date('2026-04-20T18:00:00.000Z'))
}

function renderDialog(
  mode: 'create' | 'schedulePrice' = 'create',
  config?: ChargeableCharConfig,
  onOpenChange = vi.fn(),
) {
  const qc = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false } } })
  return render(
    <QueryClientProvider client={qc}>
      <ChargeableCharFormDialog
        open={true}
        mode={mode}
        config={config}
        onOpenChange={onOpenChange}
      />
    </QueryClientProvider>,
  )
}

describe('ChargeableCharFormDialog — create mode', () => {
  beforeEach(() => setupFakeTimers())

  it('shows validation error when pricePerUnit is 0', async () => {
    renderDialog('create')

    const priceInput = screen.getByRole('spinbutton', { name: /precio/i })
    await userEvent.clear(priceInput)
    await userEvent.type(priceInput, '0')

    const dateInput = screen.getByLabelText(/vigente desde/i)
    await userEvent.clear(dateInput)
    await userEvent.type(dateInput, '2026-04-25')

    await userEvent.click(screen.getByRole('button', { name: /guardar/i }))

    await waitFor(() =>
      expect(screen.getByText(/debe ser mayor/i)).toBeInTheDocument(),
    { timeout: 3000 })
  })

  it('shows validation error when validFrom is in the past', async () => {
    renderDialog('create')

    const priceInput = screen.getByRole('spinbutton', { name: /precio/i })
    await userEvent.clear(priceInput)
    await userEvent.type(priceInput, '1.5')

    const dateInput = screen.getByLabelText(/vigente desde/i)
    await userEvent.clear(dateInput)
    await userEvent.type(dateInput, '2026-04-19')

    await userEvent.click(screen.getByRole('button', { name: /guardar/i }))

    await waitFor(() =>
      expect(screen.getByText(/anterior a hoy/i)).toBeInTheDocument(),
    { timeout: 3000 })
  })

  it('happy path calls mutation with correct yyyy-MM-dd string payload (productTypeId: null)', async () => {
    let capturedBody: unknown = null
    server.use(
      http.post(`${API_URL}/api/v1/admin/chargeable-chars`, async ({ request }) => {
        capturedBody = await request.json()
        return HttpResponse.json(
          {
            id: 1, productTypeId: null, symbol: '$', category: 'Currency',
            pricePerUnit: 1.5, validFrom: '2026-04-25', validTo: null, isActive: true,
          },
          { status: 201 },
        )
      }),
    )

    const onOpenChange = vi.fn()
    renderDialog('create', undefined, onOpenChange)

    // Fill symbol
    const symbolInput = screen.getByRole('textbox', { name: /símbolo/i })
    await userEvent.clear(symbolInput)
    await userEvent.type(symbolInput, '$')

    // Select category via Radix Select
    await userEvent.click(screen.getByRole('combobox', { name: /categoría/i }))
    await userEvent.click(screen.getByRole('option', { name: /moneda/i }))

    // Fill price
    const priceInput = screen.getByRole('spinbutton', { name: /precio/i })
    await userEvent.clear(priceInput)
    await userEvent.type(priceInput, '1.5')

    // Fill date
    const dateInput = screen.getByLabelText(/vigente desde/i)
    await userEvent.clear(dateInput)
    await userEvent.type(dateInput, '2026-04-25')

    await userEvent.click(screen.getByRole('button', { name: /^guardar$/i }))

    await waitFor(() => {
      expect(capturedBody).toBeTruthy()
      const body = capturedBody as Record<string, unknown>
      expect(body['validFrom']).toBe('2026-04-25')
      expect(typeof body['validFrom']).toBe('string')
      // productTypeId defaults to null (Global)
      expect(body['productTypeId']).toBeNull()
    }, { timeout: 8000 })

    await waitFor(() => expect(onOpenChange).toHaveBeenCalledWith(false), { timeout: 8000 })
  }, 15000)

  it('shows inline message on server 409 (ForwardOnly)', async () => {
    server.use(
      http.post(`${API_URL}/api/v1/admin/chargeable-chars`, () =>
        HttpResponse.json(
          { error: 'chargeable_char_forward_only', message: 'No se pueden retrodatar precios.' },
          { status: 409 },
        ),
      ),
    )

    renderDialog('create')

    // Fill symbol
    const symbolInput = screen.getByRole('textbox', { name: /símbolo/i })
    await userEvent.clear(symbolInput)
    await userEvent.type(symbolInput, '$')

    // Select category
    await userEvent.click(screen.getByRole('combobox', { name: /categoría/i }))
    await userEvent.click(screen.getByRole('option', { name: /moneda/i }))

    const priceInput = screen.getByRole('spinbutton', { name: /precio/i })
    await userEvent.clear(priceInput)
    await userEvent.type(priceInput, '1.5')

    const dateInput = screen.getByLabelText(/vigente desde/i)
    await userEvent.clear(dateInput)
    await userEvent.type(dateInput, '2026-04-25')

    await userEvent.click(screen.getByRole('button', { name: /^guardar$/i }))

    await waitFor(() =>
      expect(screen.getByText(/retrodatar/i)).toBeInTheDocument(),
    { timeout: 8000 })
  }, 15000)
})

describe('ChargeableCharFormDialog — schedulePrice mode', () => {
  beforeEach(() => setupFakeTimers())

  it('hides symbol and category inputs (read-only mode)', () => {
    const existingConfig: ChargeableCharConfig = {
      id: 5, productTypeId: null, symbol: '%', category: 'Percentage',
      pricePerUnit: 1.0, validFrom: '2026-01-01', validTo: null, isActive: true,
    }
    renderDialog('schedulePrice', existingConfig)

    // Should not show editable symbol/category inputs in schedulePrice mode
    expect(screen.queryByLabelText(/símbolo/i)).not.toBeInTheDocument()
    expect(screen.queryByLabelText(/categoría/i)).not.toBeInTheDocument()

    // Price and date should still be present
    expect(screen.getByRole('spinbutton', { name: /precio/i })).toBeInTheDocument()
    expect(screen.getByLabelText(/vigente desde/i)).toBeInTheDocument()
  })

  it('happy path schedulePrice calls PUT endpoint with correct payload', async () => {
    let capturedBody: unknown = null
    server.use(
      http.put(`${API_URL}/api/v1/admin/chargeable-chars/5/price`, async ({ request }) => {
        capturedBody = await request.json()
        return HttpResponse.json(
          { created: { id: 6, productTypeId: null, symbol: '%', category: 'Percentage', pricePerUnit: 2.0, validFrom: '2026-04-25', validTo: null, isActive: true }, closed: null },
        )
      }),
    )

    const existingConfig: ChargeableCharConfig = {
      id: 5, productTypeId: null, symbol: '%', category: 'Percentage',
      pricePerUnit: 1.0, validFrom: '2026-01-01', validTo: null, isActive: true,
    }
    const onOpenChange = vi.fn()
    renderDialog('schedulePrice', existingConfig, onOpenChange)

    const priceInput = screen.getByRole('spinbutton', { name: /precio/i })
    await userEvent.clear(priceInput)
    await userEvent.type(priceInput, '2')

    const dateInput = screen.getByLabelText(/vigente desde/i)
    await userEvent.clear(dateInput)
    await userEvent.type(dateInput, '2026-04-25')

    await userEvent.click(screen.getByRole('button', { name: /guardar/i }))

    await waitFor(() => {
      expect(capturedBody).toBeTruthy()
      const body = capturedBody as Record<string, unknown>
      expect(body['newValidFrom']).toBe('2026-04-25')
    }, { timeout: 5000 })

    await waitFor(() => expect(onOpenChange).toHaveBeenCalledWith(false), { timeout: 5000 })
  })
})
