import { describe, it, expect, beforeAll, afterAll, afterEach, vi } 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 { MemoryRouter, Routes, Route } from 'react-router-dom'
import React from 'react'
import { ProductTypesPage } from '../../../features/product-types/pages/ProductTypesPage'
import { useAuthStore } from '../../../stores/authStore'
import type { ProductTypeListItem, ProductTypeDetail, PagedResult } from '../../../features/product-types/types'

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

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

const adminUser = {
  id: 1,
  username: 'admin',
  nombre: 'Admin',
  rol: 'admin',
  permisos: ['catalogo:tipos:gestionar'],
  mustChangePassword: false,
}

const regularUser = {
  id: 2,
  username: 'viewer',
  nombre: 'Viewer',
  rol: 'viewer',
  permisos: [],
  mustChangePassword: false,
}

const mockItem: ProductTypeListItem = {
  id: 1,
  nombre: 'Clasificados',
  hasDuration: true,
  requiresText: true,
  requiresCategory: false,
  isBundle: false,
  allowImages: false,
  isActive: true,
}

const mockDetail: ProductTypeDetail = {
  id: 1,
  nombre: 'Clasificados',
  hasDuration: true,
  requiresText: true,
  requiresCategory: false,
  isBundle: false,
  allowImages: true,
  maxImages: 7,
  maxImageSizeMB: 5,
  maxImageWidth: 800,
  maxImageHeight: 600,
  isActive: true,
  fechaCreacion: '2024-01-01T00:00:00Z',
  fechaModificacion: null,
}

const mockPaged: PagedResult<ProductTypeListItem> = {
  items: [mockItem],
  page: 1,
  pageSize: 20,
  total: 1,
}

const emptyPaged: PagedResult<ProductTypeListItem> = {
  items: [],
  page: 1,
  pageSize: 20,
  total: 0,
}

const server = setupServer()

beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
afterEach(() => {
  server.resetHandlers()
  useAuthStore.getState().clearAuth()
  vi.clearAllMocks()
})
afterAll(() => server.close())

function renderPage(user = adminUser) {
  useAuthStore.setState({ user })
  const qc = new QueryClient({
    defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
  })
  return render(
    <QueryClientProvider client={qc}>
      <MemoryRouter initialEntries={['/admin/product-types']}>
        <Routes>
          <Route path="/admin/product-types" element={<ProductTypesPage />} />
        </Routes>
      </MemoryRouter>
    </QueryClientProvider>,
  )
}

// ─── Loading / Error / Data states ──────────────────────────────────────────

describe('ProductTypesPage — loading and error states', () => {
  it('renders loading skeleton while fetching', () => {
    server.use(
      http.get(`${API_URL}/api/v1/product-types`, async () => {
        await new Promise(() => {})
        return HttpResponse.json(emptyPaged)
      }),
    )
    renderPage()
    // During loading, skeletons are shown — verify they are rendered
    const skeletons = document.querySelectorAll('[class*="skeleton"], .animate-pulse')
    expect(skeletons.length).toBeGreaterThan(0)
  })

  it('renders data when loaded', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/product-types`, () => HttpResponse.json(mockPaged)),
    )
    renderPage()
    await waitFor(() => expect(screen.getByText('Clasificados')).toBeInTheDocument())
  })

  it('shows error state on fetch failure', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/product-types`, () =>
        HttpResponse.json({ error: 'server_error' }, { status: 500 }),
      ),
    )
    renderPage()
    await waitFor(() =>
      expect(screen.getByText(/error al cargar tipos de producto/i)).toBeInTheDocument(),
    )
  })

  it('shows empty state when no product types', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/product-types`, () => HttpResponse.json(emptyPaged)),
    )
    renderPage()
    await waitFor(() =>
      expect(screen.getByText(/no hay tipos de producto/i)).toBeInTheDocument(),
    )
  })
})

// ─── Create dialog ───────────────────────────────────────────────────────────

describe('ProductTypesPage — create dialog', () => {
  it('opens create dialog when "Nuevo Tipo" button is clicked', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/product-types`, () => HttpResponse.json(mockPaged)),
    )
    renderPage(adminUser)
    await waitFor(() => expect(screen.getByRole('button', { name: /nuevo tipo/i })).toBeInTheDocument())
    await userEvent.click(screen.getByRole('button', { name: /nuevo tipo/i }))
    await waitFor(() =>
      expect(screen.getByRole('heading', { name: /nuevo tipo de producto/i })).toBeInTheDocument(),
    )
  })

  it('opens create dialog from empty state CTA', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/product-types`, () => HttpResponse.json(emptyPaged)),
    )
    renderPage(adminUser)
    await waitFor(() => expect(screen.getByRole('button', { name: /crear primer tipo/i })).toBeInTheDocument())
    await userEvent.click(screen.getByRole('button', { name: /crear primer tipo/i }))
    await waitFor(() =>
      expect(screen.getByRole('heading', { name: /nuevo tipo de producto/i })).toBeInTheDocument(),
    )
  })

  it('hides "Nuevo Tipo" button when user lacks permission', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/product-types`, () => HttpResponse.json(mockPaged)),
    )
    renderPage(regularUser)
    await waitFor(() => expect(screen.getByText('Clasificados')).toBeInTheDocument())
    expect(screen.queryByRole('button', { name: /nuevo tipo/i })).not.toBeInTheDocument()
  })
})

// ─── Edit dialog ─────────────────────────────────────────────────────────────

describe('ProductTypesPage — edit dialog', () => {
  it('opens edit dialog pre-filled when Edit button is clicked', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/product-types`, () => HttpResponse.json(mockPaged)),
      http.get(`${API_URL}/api/v1/product-types/1`, () => HttpResponse.json(mockDetail)),
    )
    renderPage(adminUser)
    await waitFor(() => expect(screen.getByText('Clasificados')).toBeInTheDocument())
    await userEvent.click(screen.getByRole('button', { name: /editar/i }))
    await waitFor(() =>
      expect(screen.getByRole('heading', { name: /editar tipo/i })).toBeInTheDocument(),
    )
    const input = screen.getByLabelText(/nombre/i) as HTMLInputElement
    expect(input.value).toBe('Clasificados')
  })

  it('fetches full detail and populates all multimedia fields in edit dialog', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/product-types`, () => HttpResponse.json(mockPaged)),
      http.get(`${API_URL}/api/v1/product-types/1`, () => HttpResponse.json(mockDetail)),
    )
    renderPage(adminUser)
    await waitFor(() => expect(screen.getByText('Clasificados')).toBeInTheDocument())
    await userEvent.click(screen.getByRole('button', { name: /editar/i }))
    await waitFor(() =>
      expect(screen.getByRole('heading', { name: /editar tipo/i })).toBeInTheDocument(),
    )
    // Multimedia fields must be populated from the fetched detail (not null)
    const maxImages = screen.getByLabelText(/máx\. imágenes/i) as HTMLInputElement
    const maxSizeMB = screen.getByLabelText(/máx\. tamaño/i) as HTMLInputElement
    const maxWidth = screen.getByLabelText(/ancho máx/i) as HTMLInputElement
    const maxHeight = screen.getByLabelText(/alto máx/i) as HTMLInputElement
    expect(maxImages.value).toBe('7')
    expect(maxSizeMB.value).toBe('5')
    expect(maxWidth.value).toBe('800')
    expect(maxHeight.value).toBe('600')
  })

  it('disables edit button while fetch is in flight', async () => {
    let resolveDetail!: (value: unknown) => void
    server.use(
      http.get(`${API_URL}/api/v1/product-types`, () => HttpResponse.json(mockPaged)),
      http.get(`${API_URL}/api/v1/product-types/1`, () =>
        new Promise((resolve) => {
          resolveDetail = () => resolve(HttpResponse.json(mockDetail))
        }),
      ),
    )
    renderPage(adminUser)
    await waitFor(() => expect(screen.getByText('Clasificados')).toBeInTheDocument())
    const editBtn = screen.getByRole('button', { name: /editar/i })
    await userEvent.click(editBtn)
    // Button should be disabled while fetch is pending
    await waitFor(() => expect(editBtn).toBeDisabled())
    // Resolve the fetch so the dialog opens and no unhandled promise remains
    resolveDetail(undefined)
    await waitFor(() =>
      expect(screen.getByRole('heading', { name: /editar tipo/i })).toBeInTheDocument(),
    )
  })

  it('shows toast error and does not open dialog when fetch fails', async () => {
    const { toast } = await import('sonner')
    server.use(
      http.get(`${API_URL}/api/v1/product-types`, () => HttpResponse.json(mockPaged)),
      http.get(`${API_URL}/api/v1/product-types/1`, () =>
        HttpResponse.json({ error: 'not_found' }, { status: 404 }),
      ),
    )
    renderPage(adminUser)
    await waitFor(() => expect(screen.getByText('Clasificados')).toBeInTheDocument())
    await userEvent.click(screen.getByRole('button', { name: /editar/i }))
    await waitFor(() => expect(toast.error).toHaveBeenCalled())
    expect(screen.queryByRole('heading', { name: /editar tipo/i })).not.toBeInTheDocument()
  })
})

// ─── Deactivate dialog ───────────────────────────────────────────────────────

describe('ProductTypesPage — deactivate dialog', () => {
  it('opens deactivate confirmation dialog when Desactivar is clicked', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/product-types`, () => HttpResponse.json(mockPaged)),
    )
    renderPage(adminUser)
    await waitFor(() => expect(screen.getByText('Clasificados')).toBeInTheDocument())
    await userEvent.click(screen.getByRole('button', { name: /desactivar/i }))
    await waitFor(() =>
      expect(screen.getByRole('heading', { name: /desactivar tipo de producto/i })).toBeInTheDocument(),
    )
  })
})
