import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest'
import { render, screen, waitFor, within } 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 } from 'react-router-dom'
import { PermisosEditor } from '../../../features/users/components/PermisosEditor'

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

// Catalog of ALL known permissions (from /api/v1/permisos)
const catalogoPermisos = [
  { id: 1, codigo: 'ventas:contado:crear', nombre: 'Crear venta contado', descripcion: null, modulo: 'ventas' },
  { id: 2, codigo: 'ventas:contado:cobrar', nombre: 'Cobrar venta contado', descripcion: null, modulo: 'ventas' },
  { id: 3, codigo: 'textos:editar', nombre: 'Editar textos', descripcion: null, modulo: 'textos' },
]

// User permisos — from /api/v1/users/42/permisos
const mockUsuarioPermisos = {
  rolPermisos: ['ventas:contado:crear', 'ventas:contado:cobrar'],
  overrides: {
    grant: ['textos:editar'],
    deny: ['ventas:contado:cobrar'],
  },
  effective: ['ventas:contado:crear', 'textos:editar'],
}

const server = setupServer()

beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
afterEach(() => server.resetHandlers())
afterAll(() => server.close())

function renderEditor(userId = 42) {
  const qc = new QueryClient({
    defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
  })
  return render(
    <QueryClientProvider client={qc}>
      <MemoryRouter>
        <PermisosEditor userId={userId} />
      </MemoryRouter>
    </QueryClientProvider>,
  )
}

function setupHandlers() {
  server.use(
    http.get(`${API_URL}/api/v1/permisos`, () => HttpResponse.json(catalogoPermisos)),
    http.get(`${API_URL}/api/v1/users/42/permisos`, () => HttpResponse.json(mockUsuarioPermisos)),
  )
}

describe('PermisosEditor', () => {
  it('calls GET /api/v1/users/:id/permisos on mount', async () => {
    let getPermisosCallCount = 0
    server.use(
      http.get(`${API_URL}/api/v1/permisos`, () => HttpResponse.json(catalogoPermisos)),
      http.get(`${API_URL}/api/v1/users/42/permisos`, () => {
        getPermisosCallCount++
        return HttpResponse.json(mockUsuarioPermisos)
      }),
    )

    renderEditor(42)

    await waitFor(() => expect(getPermisosCallCount).toBe(1))
  })

  it('renders permissions grouped by module', async () => {
    setupHandlers()
    renderEditor()

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

    expect(screen.getByText('textos')).toBeInTheDocument()
    expect(screen.getByText('Crear venta contado')).toBeInTheDocument()
    expect(screen.getByText('Cobrar venta contado')).toBeInTheDocument()
    expect(screen.getByText('Editar textos')).toBeInTheDocument()
  })

  it('shows Heredado state for permissions in role but not in grant or deny', async () => {
    setupHandlers()
    renderEditor()

    // ventas:contado:crear is in rolPermisos, not in grant, not in deny
    // expect a button/element indicating "Heredado" is active for that permission
    await waitFor(() => expect(screen.getByText('Crear venta contado')).toBeInTheDocument())

    // Should have a "Heredado" indicator active for ventas:contado:crear
    // We look for the specific row container and check the selected state
    const crearRow = screen.getByTestId('permiso-row-ventas:contado:crear')
    expect(within(crearRow).getByRole('button', { name: /heredado/i })).toHaveAttribute(
      'aria-pressed',
      'true',
    )
  })

  it('shows Concedido state for permissions in grant', async () => {
    setupHandlers()
    renderEditor()

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

    const editarRow = screen.getByTestId('permiso-row-textos:editar')
    expect(within(editarRow).getByRole('button', { name: /concedido/i })).toHaveAttribute(
      'aria-pressed',
      'true',
    )
  })

  it('shows Denegado state for permissions in deny', async () => {
    setupHandlers()
    renderEditor()

    await waitFor(() => expect(screen.getByText('Cobrar venta contado')).toBeInTheDocument())

    const cobrarRow = screen.getByTestId('permiso-row-ventas:contado:cobrar')
    expect(within(cobrarRow).getByRole('button', { name: /denegado/i })).toHaveAttribute(
      'aria-pressed',
      'true',
    )
  })

  it('Guardar button calls PUT with correct { grant, deny } body', async () => {
    let capturedBody: unknown = null
    server.use(
      http.get(`${API_URL}/api/v1/permisos`, () => HttpResponse.json(catalogoPermisos)),
      http.get(`${API_URL}/api/v1/users/42/permisos`, () => HttpResponse.json(mockUsuarioPermisos)),
      http.put(`${API_URL}/api/v1/users/42/permisos/overrides`, async ({ request }) => {
        capturedBody = await request.json()
        return HttpResponse.json(mockUsuarioPermisos)
      }),
    )

    const u = userEvent.setup()
    renderEditor()

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

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

    await waitFor(() => expect(capturedBody).not.toBeNull())
    // Initial state: grant=['textos:editar'], deny=['ventas:contado:cobrar']
    expect(capturedBody).toMatchObject({
      grant: expect.arrayContaining(['textos:editar']),
      deny: expect.arrayContaining(['ventas:contado:cobrar']),
    })
  })

  it('shows alert on 400 invalid-permiso-codes error', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/permisos`, () => HttpResponse.json(catalogoPermisos)),
      http.get(`${API_URL}/api/v1/users/42/permisos`, () => HttpResponse.json(mockUsuarioPermisos)),
      http.put(`${API_URL}/api/v1/users/42/permisos/overrides`, () =>
        HttpResponse.json(
          { title: 'invalid-permiso-codes', status: 400, invalidCodes: ['fake:codigo'] },
          { status: 400 },
        ),
      ),
    )

    const u = userEvent.setup()
    renderEditor()

    await waitFor(() => expect(screen.getByText('Guardar cambios')).toBeInTheDocument())
    await u.click(screen.getByRole('button', { name: /guardar cambios/i }))

    await waitFor(() =>
      expect(screen.getByRole('alert')).toBeInTheDocument(),
    )
  })

  it('shows alert on 400 grant-deny-overlap error', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/permisos`, () => HttpResponse.json(catalogoPermisos)),
      http.get(`${API_URL}/api/v1/users/42/permisos`, () => HttpResponse.json(mockUsuarioPermisos)),
      http.put(`${API_URL}/api/v1/users/42/permisos/overrides`, () =>
        HttpResponse.json(
          { title: 'grant-deny-overlap', status: 400, overlap: ['textos:editar'] },
          { status: 400 },
        ),
      ),
    )

    const u = userEvent.setup()
    renderEditor()

    await waitFor(() => expect(screen.getByText('Guardar cambios')).toBeInTheDocument())
    await u.click(screen.getByRole('button', { name: /guardar cambios/i }))

    await waitFor(() =>
      expect(screen.getByRole('alert')).toBeInTheDocument(),
    )
  })
})
