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 { UsersListPage } from '../../../features/users/pages/UsersListPage'
import { useAuthStore } from '../../../stores/authStore'

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

const mockNavigate = vi.fn()
vi.mock('react-router-dom', async (importOriginal) => {
  const actual = await importOriginal<typeof import('react-router-dom')>()
  return { ...actual, useNavigate: () => mockNavigate }
})

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

function makeItems(n: number) {
  return Array.from({ length: n }, (_, i) => ({
    id: i + 1,
    username: `user${i + 1}`,
    nombre: `Nombre${i + 1}`,
    apellido: `Apellido${i + 1}`,
    email: `user${i + 1}@test.com`,
    rol: 'cajero',
    activo: true,
    ultimoLogin: null,
  }))
}

const server = setupServer()

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

function renderPage() {
  useAuthStore.setState({ user: adminUser })
  const qc = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false } } })
  return render(
    <QueryClientProvider client={qc}>
      <MemoryRouter initialEntries={['/usuarios']}>
        <Routes>
          <Route path="/usuarios" element={<UsersListPage />} />
          <Route path="/usuarios/:id/editar" element={<div>Edit Page</div>} />
        </Routes>
      </MemoryRouter>
    </QueryClientProvider>,
  )
}

describe('UsersListPage', () => {
  it('renders 5 rows when API returns 5 items', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/users`, () =>
        HttpResponse.json({ items: makeItems(5), page: 1, pageSize: 20, total: 5 }),
      ),
    )

    renderPage()

    await waitFor(() => expect(screen.getByText('user1')).toBeInTheDocument())
    // All 5 usernames visible
    for (let i = 1; i <= 5; i++) {
      expect(screen.getByText(`user${i}`)).toBeInTheDocument()
    }
  })

  it('shows empty state when items is empty', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/users`, () =>
        HttpResponse.json({ items: [], page: 1, pageSize: 20, total: 0 }),
      ),
    )

    renderPage()

    await waitFor(() => expect(screen.getByText(/sin resultados|no se encontraron/i)).toBeInTheDocument())
  })

  it('prev button disabled on first page', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/users`, () =>
        HttpResponse.json({ items: makeItems(5), page: 1, pageSize: 20, total: 5 }),
      ),
    )

    renderPage()

    await waitFor(() => expect(screen.getByText('user1')).toBeInTheDocument())

    const prevBtn = screen.getByRole('button', { name: /anterior|prev/i })
    expect(prevBtn).toBeDisabled()
  })

  it('next button disabled when on last page', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/users`, () =>
        HttpResponse.json({ items: makeItems(5), page: 1, pageSize: 20, total: 5 }),
      ),
    )

    renderPage()

    await waitFor(() => expect(screen.getByText('user1')).toBeInTheDocument())

    const nextBtn = screen.getByRole('button', { name: /siguiente|next/i })
    expect(nextBtn).toBeDisabled()
  })

  it('next button enabled when more pages exist, click requests page 2', async () => {
    const requests: string[] = []
    server.use(
      http.get(`${API_URL}/api/v1/users`, ({ request }) => {
        requests.push(request.url)
        const url = new URL(request.url)
        const page = parseInt(url.searchParams.get('page') ?? '1')
        return HttpResponse.json({
          items: makeItems(3),
          page,
          pageSize: 3,
          total: 6,
        })
      }),
    )

    const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } })
    useAuthStore.setState({ user: adminUser })
    render(
      <QueryClientProvider client={qc}>
        <MemoryRouter>
          <UsersListPage />
        </MemoryRouter>
      </QueryClientProvider>,
    )

    await waitFor(() => expect(screen.getByText('user1')).toBeInTheDocument())

    const nextBtn = screen.getByRole('button', { name: /siguiente|next/i })
    expect(nextBtn).not.toBeDisabled()

    await userEvent.click(nextBtn)

    await waitFor(() => {
      const page2Req = requests.find((u) => u.includes('page=2'))
      expect(page2Req).toBeTruthy()
    })
  })

  it('selecting rol filter adds querystring rol', async () => {
    const requests: string[] = []
    server.use(
      http.get(`${API_URL}/api/v1/users`, ({ request }) => {
        requests.push(request.url)
        return HttpResponse.json({ items: [], page: 1, pageSize: 20, total: 0 })
      }),
    )

    renderPage()

    await waitFor(() => expect(requests.length).toBeGreaterThan(0))

    // Open the Radix Select trigger and pick "Admin"
    const rolTrigger = screen.getByRole('combobox', { name: /rol/i })
    await userEvent.click(rolTrigger)
    await userEvent.click(screen.getByRole('option', { name: /^admin$/i }))

    await waitFor(() => {
      const filtered = requests.find((u) => u.includes('rol=admin'))
      expect(filtered).toBeTruthy()
    })
  })

  it('typing in search input triggers request with search param (debounced)', async () => {
    const requests: string[] = []
    server.use(
      http.get(`${API_URL}/api/v1/users`, ({ request }) => {
        requests.push(request.url)
        return HttpResponse.json({ items: [], page: 1, pageSize: 20, total: 0 })
      }),
    )

    renderPage()
    await waitFor(() => expect(requests.length).toBeGreaterThan(0))

    const searchInput = screen.getByPlaceholderText(/buscar/i)
    // Use fireEvent to type quickly without delay — then wait for debounce naturally
    await userEvent.type(searchInput, 'juan')

    // After debounce (300ms + render cycles), should include search param
    await waitFor(
      () => {
        const searched = requests.find((u) => u.includes('search='))
        expect(searched).toBeTruthy()
      },
      { timeout: 3000 },
    )
  }, 10000)

  it('click row navigates to edit page', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/users`, () =>
        HttpResponse.json({ items: makeItems(2), page: 1, pageSize: 20, total: 2 }),
      ),
    )

    renderPage()

    // Wait for data to load
    await waitFor(() => expect(screen.getByText('user1')).toBeInTheDocument())

    // Click on the username cell which is inside the row
    const usernameCell = screen.getByText('user1')
    await userEvent.click(usernameCell)

    expect(mockNavigate).toHaveBeenCalledWith('/usuarios/1/editar')
  })
})
