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 { UserEditPage } from '../../../features/users/pages/UserEditPage'
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,
}

const mockUserDetail = {
  id: 5,
  username: 'cajero1',
  nombre: 'Juan',
  apellido: 'Pérez',
  email: 'j@x.com',
  rol: 'cajero',
  activo: true,
  mustChangePassword: false,
  ultimoLogin: null,
  fechaModificacion: null,
}

const server = setupServer()

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

function renderEditPage(userId = 5) {
  useAuthStore.setState({ user: adminUser })
  const qc = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false } } })
  return render(
    <QueryClientProvider client={qc}>
      <MemoryRouter initialEntries={[`/usuarios/${userId}/editar`]}>
        <Routes>
          <Route path="/usuarios/:id/editar" element={<UserEditPage />} />
          <Route path="/usuarios" element={<div>Users List</div>} />
        </Routes>
      </MemoryRouter>
    </QueryClientProvider>,
  )
}

describe('UserEditPage', () => {
  it('prefills form with user data', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/users/5`, () => HttpResponse.json(mockUserDetail)),
    )

    renderEditPage()

    await waitFor(() => expect(screen.getByDisplayValue('Juan')).toBeInTheDocument())
    expect(screen.getByDisplayValue('Pérez')).toBeInTheDocument()
    expect(screen.getByDisplayValue('j@x.com')).toBeInTheDocument()
  })

  it('username field is displayed but not an editable input', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/users/5`, () => HttpResponse.json(mockUserDetail)),
    )

    renderEditPage()

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

    // Username should NOT be an editable input
    const inputs = screen.queryAllByRole('textbox')
    const usernameInput = inputs.find((el) => (el as HTMLInputElement).value === 'cajero1')
    expect(usernameInput).toBeUndefined()
  })

  it('submit calls PUT with correct payload then navigates to /usuarios', async () => {
    let capturedBody: unknown = null
    server.use(
      http.get(`${API_URL}/api/v1/users/5`, () => HttpResponse.json(mockUserDetail)),
      http.put(`${API_URL}/api/v1/users/5`, async ({ request }) => {
        capturedBody = await request.json()
        return HttpResponse.json({ ...mockUserDetail, nombre: 'Pedro' })
      }),
    )

    renderEditPage()

    await waitFor(() => expect(screen.getByDisplayValue('Juan')).toBeInTheDocument())

    // Clear and update nombre
    const nombreInput = screen.getByDisplayValue('Juan')
    await userEvent.clear(nombreInput)
    await userEvent.type(nombreInput, 'Pedro')

    // Confirm the rol Select has a value by opening and re-selecting the prefilled value
    // (Radix Select in jsdom needs interaction to register the item text in context)
    const rolTrigger = screen.getByRole('combobox', { name: /rol/i })
    await userEvent.click(rolTrigger)
    await waitFor(() => expect(screen.getByRole('option', { name: /cajero/i })).toBeInTheDocument())
    await userEvent.click(screen.getByRole('option', { name: /cajero/i }))

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

    await waitFor(() => expect(mockNavigate).toHaveBeenCalledWith('/usuarios'))
    expect(capturedBody).toMatchObject({ nombre: 'Pedro' })
  })

  it('shows last-admin-lockout error message on 400', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/users/5`, () => HttpResponse.json(mockUserDetail)),
      http.put(`${API_URL}/api/v1/users/5`, () =>
        HttpResponse.json(
          { title: 'last-admin-lockout', status: 400 },
          { status: 400 },
        ),
      ),
    )

    renderEditPage()

    await waitFor(() => expect(screen.getByDisplayValue('Juan')).toBeInTheDocument())

    // Confirm rol by opening and re-selecting the prefilled value
    const rolTrigger = screen.getByRole('combobox', { name: /rol/i })
    await userEvent.click(rolTrigger)
    await waitFor(() => expect(screen.getByRole('option', { name: /cajero/i })).toBeInTheDocument())
    await userEvent.click(screen.getByRole('option', { name: /cajero/i }))

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

    await waitFor(() =>
      expect(screen.getByText(/último administrador|last.admin.lockout/i)).toBeInTheDocument(),
    )

    // Should NOT navigate
    expect(mockNavigate).not.toHaveBeenCalled()
  })

  it('shows "Resetear contraseña" button when editing another user', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/users/5`, () => HttpResponse.json(mockUserDetail)),
    )

    renderEditPage(5)

    await waitFor(() => expect(screen.getByDisplayValue('Juan')).toBeInTheDocument())
    expect(screen.getByRole('button', { name: /resetear contraseña/i })).toBeInTheDocument()
  })

  it('hides "Resetear contraseña" button when editing own profile', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/users/1`, () =>
        HttpResponse.json({ ...mockUserDetail, id: 1, username: 'admin' }),
      ),
    )

    renderEditPage(1)

    await waitFor(() => expect(screen.getByDisplayValue('Juan')).toBeInTheDocument())
    expect(screen.queryByRole('button', { name: /resetear contraseña/i })).not.toBeInTheDocument()
  })

  // FP-01: tabs Perfil and Permisos visible when editing another user
  it('shows tabs "Perfil" and "Permisos" when editing another user', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/users/5`, () => HttpResponse.json(mockUserDetail)),
      http.get(`${API_URL}/api/v1/users/5/permisos`, () =>
        HttpResponse.json({
          usuarioId: 5, rol: 'cajero', rolPermisos: [], grant: [], deny: [], effective: [],
        }),
      ),
      http.get(`${API_URL}/api/v1/permisos`, () => HttpResponse.json([])),
    )

    renderEditPage(5)

    await waitFor(() => expect(screen.getByDisplayValue('Juan')).toBeInTheDocument())

    expect(screen.getByRole('tab', { name: /perfil/i })).toBeInTheDocument()
    expect(screen.getByRole('tab', { name: /permisos/i })).toBeInTheDocument()
  })

  // FP-10: self-edit — tab Permisos is disabled
  it('disables tab "Permisos" when editing own profile (self-edit)', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/users/1`, () =>
        HttpResponse.json({ ...mockUserDetail, id: 1, username: 'admin' }),
      ),
    )

    renderEditPage(1)

    await waitFor(() => expect(screen.getByDisplayValue('Juan')).toBeInTheDocument())

    const permisosTab = screen.getByRole('tab', { name: /permisos/i })
    expect(permisosTab).toBeDisabled()
  })
})
