import { describe, it, expect, beforeAll, afterAll, afterEach, vi } 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 { useRubrosTree } from '../../../features/rubros/hooks/useRubrosTree'
import { useCreateRubro } from '../../../features/rubros/hooks/useCreateRubro'
import { useUpdateRubro } from '../../../features/rubros/hooks/useUpdateRubro'
import { useDeleteRubro } from '../../../features/rubros/hooks/useDeleteRubro'
import { useMoveRubro } from '../../../features/rubros/hooks/useMoveRubro'
import type { RubroTreeNode, Rubro } from '../../../features/rubros/types'

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

const mockTree: RubroTreeNode[] = [
  { id: 1, nombre: 'Autos', orden: 1, activo: true, parentId: null, tarifarioBaseId: null, hijos: [] },
]

const mockRubro: Rubro = {
  id: 1,
  nombre: 'Autos',
  orden: 1,
  activo: true,
  parentId: null,
  tarifarioBaseId: null,
  fechaCreacion: '2026-04-18T00:00:00Z',
  fechaModificacion: null,
}

const server = setupServer()

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

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

describe('useRubrosTree', () => {
  it('returns tree data on success', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/rubros/tree`, () => HttpResponse.json(mockTree)),
    )
    const { result } = renderHook(() => useRubrosTree(), { wrapper: makeWrapper() })
    await waitFor(() => expect(result.current.isSuccess).toBe(true))
    expect(result.current.data).toEqual(mockTree)
  })

  it('returns error state on failure', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/rubros/tree`, () =>
        HttpResponse.json({ error: 'server_error' }, { status: 500 }),
      ),
    )
    const { result } = renderHook(() => useRubrosTree(), { wrapper: makeWrapper() })
    await waitFor(() => expect(result.current.isError).toBe(true))
  })

  it('passes incluirInactivos param when true', async () => {
    let capturedUrl = ''
    server.use(
      http.get(`${API_URL}/api/v1/rubros/tree`, ({ request }) => {
        capturedUrl = request.url
        return HttpResponse.json(mockTree)
      }),
    )
    const { result } = renderHook(() => useRubrosTree(true), { wrapper: makeWrapper() })
    await waitFor(() => expect(result.current.isSuccess).toBe(true))
    expect(capturedUrl).toContain('incluirInactivos=true')
  })
})

describe('useCreateRubro', () => {
  it('calls createRubro and invalidates rubros queries on success', async () => {
    server.use(
      http.post(`${API_URL}/api/v1/admin/rubros`, () =>
        HttpResponse.json(mockRubro, { status: 201 }),
      ),
    )
    const qc = new QueryClient({
      defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
    })
    const invalidateSpy = vi.spyOn(qc, 'invalidateQueries')
    const wrapper = ({ children }: { children: React.ReactNode }) =>
      React.createElement(QueryClientProvider, { client: qc }, children)

    const { result } = renderHook(() => useCreateRubro(), { wrapper })
    await act(async () => {
      result.current.mutate({ nombre: 'Autos', parentId: null, tarifarioBaseId: null })
    })
    await waitFor(() => expect(result.current.isSuccess).toBe(true))
    expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['rubros'] })
  })
})

describe('useUpdateRubro', () => {
  it('calls updateRubro and invalidates rubros queries on success', async () => {
    server.use(
      http.put(`${API_URL}/api/v1/admin/rubros/1`, () =>
        HttpResponse.json(mockRubro),
      ),
    )
    const qc = new QueryClient({
      defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
    })
    const invalidateSpy = vi.spyOn(qc, 'invalidateQueries')
    const wrapper = ({ children }: { children: React.ReactNode }) =>
      React.createElement(QueryClientProvider, { client: qc }, children)

    const { result } = renderHook(() => useUpdateRubro(), { wrapper })
    await act(async () => {
      result.current.mutate({ id: 1, data: { nombre: 'Autos Actualizado', tarifarioBaseId: null } })
    })
    await waitFor(() => expect(result.current.isSuccess).toBe(true))
    expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['rubros'] })
  })
})

describe('useDeleteRubro', () => {
  it('calls deleteRubro and invalidates rubros queries on success', async () => {
    server.use(
      http.delete(`${API_URL}/api/v1/admin/rubros/1`, () =>
        new HttpResponse(null, { status: 204 }),
      ),
    )
    const qc = new QueryClient({
      defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
    })
    const invalidateSpy = vi.spyOn(qc, 'invalidateQueries')
    const wrapper = ({ children }: { children: React.ReactNode }) =>
      React.createElement(QueryClientProvider, { client: qc }, children)

    const { result } = renderHook(() => useDeleteRubro(), { wrapper })
    await act(async () => {
      result.current.mutate(1)
    })
    await waitFor(() => expect(result.current.isSuccess).toBe(true))
    expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['rubros'] })
  })
})

describe('useMoveRubro', () => {
  it('calls moveRubro and invalidates rubros queries on success', async () => {
    server.use(
      http.patch(`${API_URL}/api/v1/admin/rubros/1/mover`, () =>
        new HttpResponse(null, { status: 200 }),
      ),
    )
    const qc = new QueryClient({
      defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
    })
    const invalidateSpy = vi.spyOn(qc, 'invalidateQueries')
    const wrapper = ({ children }: { children: React.ReactNode }) =>
      React.createElement(QueryClientProvider, { client: qc }, children)

    const { result } = renderHook(() => useMoveRubro(), { wrapper })
    await act(async () => {
      result.current.mutate({ id: 1, data: { nuevoParentId: null, nuevoOrden: 1 } })
    })
    await waitFor(() => expect(result.current.isSuccess).toBe(true))
    expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['rubros'] })
  })
})
