import { describe, it, expect, vi } from 'vitest'
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { MemoryRouter } from 'react-router-dom'
import React from 'react'
import { RubroFormDialog } from '../../../features/rubros/components/RubroFormDialog'
import { DeleteRubroDialog } from '../../../features/rubros/components/DeleteRubroDialog'
import type { Rubro } from '../../../features/rubros/types'

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

const sampleRubro: Rubro = {
  id: 1,
  nombre: 'Autos',
  orden: 1,
  activo: true,
  parentId: null,
  tarifarioBaseId: null,
  fechaCreacion: '2026-04-18T00:00:00Z',
  fechaModificacion: null,
}

function wrap(children: React.ReactNode) {
  const qc = new QueryClient({
    defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
  })
  return render(
    <QueryClientProvider client={qc}>
      <MemoryRouter>{children}</MemoryRouter>
    </QueryClientProvider>,
  )
}

// ─── RubroFormDialog — CREATE mode ──────────────────────────────────────────

describe('RubroFormDialog — create mode', () => {
  it('renders form in create mode when no rubro prop', () => {
    wrap(
      <RubroFormDialog open={true} onOpenChange={vi.fn()} onSubmit={vi.fn()} />,
    )
    expect(screen.getByRole('heading', { name: /nuevo rubro/i })).toBeInTheDocument()
  })

  it('calls onSubmit with correct payload on valid submit', async () => {
    const onSubmit = vi.fn()
    wrap(
      <RubroFormDialog open={true} onOpenChange={vi.fn()} onSubmit={onSubmit} />,
    )
    await userEvent.type(screen.getByLabelText(/nombre/i), 'Categoría Nueva')
    await userEvent.click(screen.getByRole('button', { name: /crear/i }))
    await waitFor(() => {
      expect(onSubmit).toHaveBeenCalled()
      const firstArg = onSubmit.mock.calls[0][0]
      expect(firstArg).toMatchObject({ nombre: 'Categoría Nueva' })
    })
  })

  it('does not call onSubmit when nombre is empty', async () => {
    const onSubmit = vi.fn()
    wrap(
      <RubroFormDialog open={true} onOpenChange={vi.fn()} onSubmit={onSubmit} />,
    )
    await userEvent.click(screen.getByRole('button', { name: /crear/i }))
    await waitFor(() => {
      expect(screen.getByText(/nombre es requerido/i)).toBeInTheDocument()
    })
    expect(onSubmit).not.toHaveBeenCalled()
  })
})

// ─── RubroFormDialog — EDIT mode ────────────────────────────────────────────

describe('RubroFormDialog — edit mode', () => {
  it('renders form in edit mode with pre-filled data', () => {
    wrap(
      <RubroFormDialog
        open={true}
        onOpenChange={vi.fn()}
        rubro={sampleRubro}
        onSubmit={vi.fn()}
      />,
    )
    expect(screen.getByRole('heading', { name: /editar rubro/i })).toBeInTheDocument()
    const input = screen.getByLabelText(/nombre/i) as HTMLInputElement
    expect(input.value).toBe('Autos')
  })

  it('calls onSubmit with correct payload on save', async () => {
    const onSubmit = vi.fn()
    wrap(
      <RubroFormDialog
        open={true}
        onOpenChange={vi.fn()}
        rubro={sampleRubro}
        onSubmit={onSubmit}
      />,
    )
    const input = screen.getByLabelText(/nombre/i) as HTMLInputElement
    await userEvent.clear(input)
    await userEvent.type(input, 'Autos Modificado')
    await userEvent.click(screen.getByRole('button', { name: /guardar cambios/i }))
    await waitFor(() => {
      expect(onSubmit).toHaveBeenCalled()
      const firstArg = onSubmit.mock.calls[0][0]
      expect(firstArg).toMatchObject({ nombre: 'Autos Modificado' })
    })
  })
})

// ─── DeleteRubroDialog ───────────────────────────────────────────────────────

describe('DeleteRubroDialog', () => {
  it('renders confirmation message with rubro name', () => {
    wrap(
      <DeleteRubroDialog
        open={true}
        onOpenChange={vi.fn()}
        rubro={sampleRubro}
        onConfirm={vi.fn()}
      />,
    )
    expect(screen.getByText(/autos/i)).toBeInTheDocument()
    // Title is present
    expect(screen.getByRole('heading', { name: /desactivar rubro/i })).toBeInTheDocument()
  })

  it('calls onConfirm when user confirms deletion', async () => {
    const onConfirm = vi.fn().mockResolvedValue(undefined)
    wrap(
      <DeleteRubroDialog
        open={true}
        onOpenChange={vi.fn()}
        rubro={sampleRubro}
        onConfirm={onConfirm}
      />,
    )
    // Click the AlertDialogAction confirm button (not cancel)
    const buttons = screen.getAllByRole('button', { name: /desactivar/i })
    const confirmBtn = buttons.find((b) => b.textContent?.trim() === 'Desactivar')!
    await userEvent.click(confirmBtn)
    await waitFor(() => expect(onConfirm).toHaveBeenCalledWith(sampleRubro.id))
  })

  it('shows inline error when backend returns 409', async () => {
    const onConfirm = vi.fn(() =>
      Promise.reject({ response: { status: 409, data: { message: 'Tiene subrubros activos' } } }),
    )
    wrap(
      <DeleteRubroDialog
        open={true}
        onOpenChange={vi.fn()}
        rubro={sampleRubro}
        onConfirm={onConfirm}
      />,
    )
    const buttons = screen.getAllByRole('button', { name: /desactivar/i })
    const confirmBtn = buttons.find((b) => b.textContent?.trim() === 'Desactivar')!
    await userEvent.click(confirmBtn)
    await waitFor(() => {
      expect(screen.getByText(/subrubros activos/i)).toBeInTheDocument()
    })
  })
})
