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 } from 'react-router-dom'
import { TooltipProvider } from '../../../../components/ui/tooltip'
import { AuditPage } from '../../../../pages/admin/audit/AuditPage'

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

// Sonner toast is mocked so we can assert it was called without rendering.
vi.mock('sonner', () => ({
  toast: {
    success: vi.fn(),
    error: vi.fn(),
  },
}))

function makeEvent(id: number, overrides: Record<string, unknown> = {}) {
  return {
    id,
    occurredAt: `2026-04-${String(10 + (id % 20)).padStart(2, '0')}T10:00:00Z`,
    actorUserId: 1,
    actorUsername: `user${id}`,
    action: 'user.created',
    targetType: 'User',
    targetId: String(100 + id),
    correlationId: `corr-${id}`,
    ipAddress: '10.0.0.1',
    metadata: null,
    ...overrides,
  }
}

const server = setupServer()

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

function renderPage() {
  const qc = new QueryClient({
    defaultOptions: {
      queries: { retry: false },
      mutations: { retry: false },
    },
  })
  return render(
    <QueryClientProvider client={qc}>
      <TooltipProvider>
        <MemoryRouter initialEntries={['/admin/audit']}>
          <AuditPage />
        </MemoryRouter>
      </TooltipProvider>
    </QueryClientProvider>,
  )
}

describe('AuditPage', () => {
  it('renders the table with rows returned by the API', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/audit/events`, () =>
        HttpResponse.json({
          items: [makeEvent(1), makeEvent(2), makeEvent(3)],
          nextCursor: null,
        }),
      ),
    )

    renderPage()

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

    // Action badge present
    const badges = screen.getAllByText('user.created')
    expect(badges.length).toBe(3)
  })

  it('shows empty state when no items are returned', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/audit/events`, () =>
        HttpResponse.json({ items: [], nextCursor: null }),
      ),
    )

    renderPage()

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

  it('applies filters on submit — sends actor + targetType as query params', async () => {
    const requests: string[] = []
    server.use(
      http.get(`${API_URL}/api/v1/audit/events`, ({ request }) => {
        requests.push(request.url)
        return HttpResponse.json({ items: [], nextCursor: null })
      }),
    )

    const u = userEvent.setup()
    renderPage()

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

    const actorInput = screen.getByLabelText(/usuario \(id\)/i)
    await u.type(actorInput, '42')

    const targetTypeInput = screen.getByLabelText(/tipo de entidad/i)
    await u.type(targetTypeInput, 'User')

    const applyBtn = screen.getByRole('button', { name: /aplicar filtros/i })
    await u.click(applyBtn)

    await waitFor(() => {
      const withFilters = requests.find(
        (url) => url.includes('actor=42') && url.includes('targetType=User'),
      )
      expect(withFilters).toBeTruthy()
    })
  })

  it('"Cargar más" is disabled when nextCursor is null', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/audit/events`, () =>
        HttpResponse.json({
          items: [makeEvent(1)],
          nextCursor: null,
        }),
      ),
    )

    renderPage()

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

    const loadMoreBtn = screen.getByRole('button', {
      name: /cargar más/i,
    })
    expect(loadMoreBtn).toBeDisabled()
  })

  it('"Cargar más" fetches next page with cursor and appends rows', async () => {
    const requests: string[] = []
    server.use(
      http.get(`${API_URL}/api/v1/audit/events`, ({ request }) => {
        requests.push(request.url)
        const url = new URL(request.url)
        const cursor = url.searchParams.get('cursor')
        if (cursor === 'cursor-page-2') {
          return HttpResponse.json({
            items: [makeEvent(10), makeEvent(11)],
            nextCursor: null,
          })
        }
        return HttpResponse.json({
          items: [makeEvent(1), makeEvent(2)],
          nextCursor: 'cursor-page-2',
        })
      }),
    )

    const u = userEvent.setup()
    renderPage()

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

    const loadMoreBtn = screen.getByRole('button', {
      name: /cargar más/i,
    })
    expect(loadMoreBtn).not.toBeDisabled()
    await u.click(loadMoreBtn)

    // Second request with cursor param
    await waitFor(() => {
      const paged = requests.find((url) =>
        url.includes('cursor=cursor-page-2'),
      )
      expect(paged).toBeTruthy()
    })

    // Appended rows appear alongside originals
    await waitFor(() =>
      expect(screen.getByText('user10')).toBeInTheDocument(),
    )
    expect(screen.getByText('user11')).toBeInTheDocument()
    // Original rows still visible (append, not replace)
    expect(screen.getByText('user1')).toBeInTheDocument()
  })

  it('shows "sistema" placeholder when actorUsername is null', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/audit/events`, () =>
        HttpResponse.json({
          items: [makeEvent(1, { actorUsername: null, actorUserId: null })],
          nextCursor: null,
        }),
      ),
    )

    renderPage()

    await waitFor(() =>
      expect(screen.getByText('sistema')).toBeInTheDocument(),
    )
  })

  it('"Limpiar" clears the form inputs', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/audit/events`, () =>
        HttpResponse.json({ items: [], nextCursor: null }),
      ),
    )

    const u = userEvent.setup()
    renderPage()

    const actorInput = screen.getByLabelText(
      /usuario \(id\)/i,
    ) as HTMLInputElement
    await u.type(actorInput, '42')
    expect(actorInput.value).toBe('42')

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

    // Form field cleared after reset
    expect(actorInput.value).toBe('')
  })
})
