import { describe, it, expect, vi, afterEach, beforeAll, afterAll } from 'vitest'
import { renderHook, waitFor, act } from '@testing-library/react'
import { http, HttpResponse } from 'msw'
import { setupServer } from 'msw/node'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import React from 'react'
import { useChargeableCharConfigs } from '../hooks/useChargeableCharConfigs'
import { useSchedulePriceChange } from '../hooks/useSchedulePriceChange'
import { useReactivateChargeableCharConfig } from '../hooks/useReactivateChargeableCharConfig'
import { useDeleteChargeableCharConfig } from '../hooks/useDeleteChargeableCharConfig'
import { ReactivationNotAllowedError } from '../api/reactivateChargeableCharConfig'

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

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

const server = setupServer()
beforeAll(() => server.listen({ onUnhandledRequest: 'warn' }))
afterEach(() => { server.resetHandlers(); vi.clearAllMocks() })
afterAll(() => server.close())

function makeWrapper() {
  const qc = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false } } })
  return {
    qc,
    wrapper: ({ children }: { children: React.ReactNode }) =>
      React.createElement(QueryClientProvider, { client: qc }, children),
  }
}

describe('useChargeableCharConfigs', () => {
  it('fetches list and returns paged result', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/admin/chargeable-chars`, () =>
        HttpResponse.json({
          items: [{ id: 1, productTypeId: null, symbol: '$', category: 'Currency', pricePerUnit: 1.5, validFrom: '2026-01-01', validTo: null, isActive: true }],
          page: 1, pageSize: 20, total: 1,
        }),
      ),
    )
    const { wrapper } = makeWrapper()
    const { result } = renderHook(() => useChargeableCharConfigs({}), { wrapper })

    await waitFor(() => expect(result.current.isSuccess).toBe(true))
    expect(result.current.data?.items).toHaveLength(1)
    expect(result.current.data?.items[0].symbol).toBe('$')
  })

  it('sends productTypeId and activeOnly as query params', async () => {
    let capturedUrl: string | null = null
    server.use(
      http.get(`${API_URL}/api/v1/admin/chargeable-chars`, ({ request }) => {
        capturedUrl = request.url
        return HttpResponse.json({ items: [], page: 1, pageSize: 20, total: 0 })
      }),
    )
    const { wrapper } = makeWrapper()
    const { result } = renderHook(
      () => useChargeableCharConfigs({ productTypeId: 3, activeOnly: true }),
      { wrapper },
    )

    await waitFor(() => expect(result.current.isSuccess).toBe(true))
    expect(capturedUrl).toContain('productTypeId=3')
    expect(capturedUrl).toContain('activeOnly=true')
  })
})

describe('useSchedulePriceChange', () => {
  it('on success invalidates both list and byId query keys', async () => {
    server.use(
      http.put(`${API_URL}/api/v1/admin/chargeable-chars/5/price`, () =>
        HttpResponse.json({
          created: { id: 6, productTypeId: null, symbol: '$', category: 'Currency', pricePerUnit: 2.0, validFrom: '2026-05-01', validTo: null, isActive: true },
          closed: null,
        }),
      ),
    )
    const { qc, wrapper } = makeWrapper()
    const invalidateSpy = vi.spyOn(qc, 'invalidateQueries')

    const { result } = renderHook(() => useSchedulePriceChange(5), { wrapper })

    await act(async () => {
      result.current.mutate({ newPricePerUnit: 2.0, newValidFrom: '2026-05-01' })
    })
    await waitFor(() => expect(result.current.isSuccess).toBe(true))

    const listInvalidated = invalidateSpy.mock.calls.some(
      ([q]) => JSON.stringify((q as { queryKey: unknown }).queryKey).includes('chargeableChars'),
    )
    expect(listInvalidated).toBe(true)
  })
})

describe('useReactivateChargeableCharConfig', () => {
  it('happy path — returns response and invalidates list + byId', async () => {
    server.use(
      http.patch(`${API_URL}/api/v1/admin/chargeable-chars/7/reactivate`, () =>
        HttpResponse.json({ id: 7, symbol: '$', productTypeId: null, pricePerUnit: 1.5, validFrom: '2026-01-01' }),
      ),
    )
    const { qc, wrapper } = makeWrapper()
    const invalidateSpy = vi.spyOn(qc, 'invalidateQueries')

    const { result } = renderHook(() => useReactivateChargeableCharConfig(), { wrapper })

    await act(async () => { result.current.mutate(7) })
    await waitFor(() => expect(result.current.isSuccess).toBe(true))
    expect(result.current.data?.id).toBe(7)

    const listInvalidated = invalidateSpy.mock.calls.some(
      ([q]) => JSON.stringify((q as { queryKey: unknown }).queryKey).includes('list'),
    )
    expect(listInvalidated).toBe(true)
  })

  it('409 ALREADY_ACTIVE — throws ReactivationNotAllowedError with correct reason', async () => {
    server.use(
      http.patch(`${API_URL}/api/v1/admin/chargeable-chars/8/reactivate`, () =>
        HttpResponse.json(
          { code: 'CHARGEABLE_CHAR_REACTIVATION_NOT_ALLOWED', reason: 'ALREADY_ACTIVE', message: 'El registro ya está activo' },
          { status: 409 },
        ),
      ),
    )
    const { wrapper } = makeWrapper()
    const { result } = renderHook(() => useReactivateChargeableCharConfig(), { wrapper })

    await act(async () => { result.current.mutate(8) })
    await waitFor(() => expect(result.current.isError).toBe(true))

    expect(result.current.error).toBeInstanceOf(ReactivationNotAllowedError)
    expect((result.current.error as ReactivationNotAllowedError).reason).toBe('ALREADY_ACTIVE')
  })

  it('409 VIGENTE_EXISTS — throws ReactivationNotAllowedError with correct reason', async () => {
    server.use(
      http.patch(`${API_URL}/api/v1/admin/chargeable-chars/9/reactivate`, () =>
        HttpResponse.json(
          { code: 'CHARGEABLE_CHAR_REACTIVATION_NOT_ALLOWED', reason: 'VIGENTE_EXISTS', message: 'Ya existe un registro activo' },
          { status: 409 },
        ),
      ),
    )
    const { wrapper } = makeWrapper()
    const { result } = renderHook(() => useReactivateChargeableCharConfig(), { wrapper })

    await act(async () => { result.current.mutate(9) })
    await waitFor(() => expect(result.current.isError).toBe(true))

    expect(result.current.error).toBeInstanceOf(ReactivationNotAllowedError)
    expect((result.current.error as ReactivationNotAllowedError).reason).toBe('VIGENTE_EXISTS')
  })
})

describe('useDeleteChargeableCharConfig', () => {
  it('happy path — returns {id} and invalidates list + removes byId', async () => {
    server.use(
      http.delete(`${API_URL}/api/v1/admin/chargeable-chars/10`, () =>
        HttpResponse.json({ id: 10 }),
      ),
    )
    const { qc, wrapper } = makeWrapper()
    const invalidateSpy = vi.spyOn(qc, 'invalidateQueries')
    const removeSpy = vi.spyOn(qc, 'removeQueries')

    const { result } = renderHook(() => useDeleteChargeableCharConfig(), { wrapper })

    await act(async () => { result.current.mutate(10) })
    await waitFor(() => expect(result.current.isSuccess).toBe(true))
    expect(result.current.data?.id).toBe(10)

    const listInvalidated = invalidateSpy.mock.calls.some(
      ([q]) => JSON.stringify((q as { queryKey: unknown }).queryKey).includes('list'),
    )
    expect(listInvalidated).toBe(true)
    expect(removeSpy).toHaveBeenCalled()
  })

  it('404 — mutation transitions to error state', async () => {
    server.use(
      http.delete(`${API_URL}/api/v1/admin/chargeable-chars/99`, () =>
        HttpResponse.json({ message: 'Not found' }, { status: 404 }),
      ),
    )
    const { wrapper } = makeWrapper()
    const { result } = renderHook(() => useDeleteChargeableCharConfig(), { wrapper })

    await act(async () => { result.current.mutate(99) })
    await waitFor(() => expect(result.current.isError).toBe(true))
  })
})
