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 { useProductPrices } from '../../../features/products/hooks/useProductPrices'
import { useAddProductPrice } from '../../../features/products/hooks/useAddProductPrice'
import type { ProductPrice, AddProductPriceResponse, PagedResult } from '../../../features/products/types'

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

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

const mockPrice: ProductPrice = {
  id: 1,
  productId: 1,
  price: 500,
  priceValidFrom: '2026-04-01',
  priceValidTo: null,
  isActive: true,
}

const mockPagedResult: PagedResult<ProductPrice> = {
  items: [mockPrice],
  page: 1,
  pageSize: 20,
  total: 1,
}

const mockResponse: AddProductPriceResponse = {
  created: { id: 2, productId: 1, price: 700, priceValidFrom: '2026-05-01', priceValidTo: null, isActive: true },
  closed: { id: 1, productId: 1, price: 500, priceValidFrom: '2026-04-01', priceValidTo: '2026-04-30', isActive: false },
}

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 { qc, wrapper: ({ children }: { children: React.ReactNode }) =>
    React.createElement(QueryClientProvider, { client: qc }, children) }
}

describe('useProductPrices', () => {
  it('fetches prices for productId and returns PagedResult data', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/products/1/prices`, () => HttpResponse.json(mockPagedResult)),
    )
    const { wrapper } = makeWrapper()
    const { result } = renderHook(() => useProductPrices(1), { wrapper })
    await waitFor(() => expect(result.current.isSuccess).toBe(true))
    expect(result.current.data).toEqual(mockPagedResult)
    expect(result.current.data?.items).toEqual([mockPrice])
  })

  it('includes page and pageSize in queryKey for correct caching', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/products/1/prices`, () => HttpResponse.json(mockPagedResult)),
    )
    const { qc, wrapper } = makeWrapper()
    const { result } = renderHook(() => useProductPrices(1, 1, 20), { wrapper })
    await waitFor(() => expect(result.current.isSuccess).toBe(true))
    // queryKey must include page and pageSize
    expect(qc.getQueryState(['product-prices', 1, 1, 20])).toBeDefined()
  })

  it('uses different cache entry for different pages', async () => {
    const page2Result: PagedResult<ProductPrice> = {
      items: [{ ...mockPrice, id: 2 }],
      page: 2,
      pageSize: 20,
      total: 21,
    }
    server.use(
      http.get(`${API_URL}/api/v1/products/1/prices`, ({ request }) => {
        const url = new URL(request.url)
        const page = url.searchParams.get('page')
        return page === '2' ? HttpResponse.json(page2Result) : HttpResponse.json(mockPagedResult)
      }),
    )
    const { qc, wrapper } = makeWrapper()

    const { result: r1 } = renderHook(() => useProductPrices(1, 1, 20), { wrapper })
    await waitFor(() => expect(r1.current.isSuccess).toBe(true))

    const { result: r2 } = renderHook(() => useProductPrices(1, 2, 20), { wrapper })
    await waitFor(() => expect(r2.current.isSuccess).toBe(true))

    // Each page is cached separately
    expect(qc.getQueryState(['product-prices', 1, 1, 20])).toBeDefined()
    expect(qc.getQueryState(['product-prices', 1, 2, 20])).toBeDefined()
    expect(r1.current.data?.page).toBe(1)
    expect(r2.current.data?.page).toBe(2)
  })

  it('is disabled when productId is 0', async () => {
    // No server handler — if the query fired it would fail with unhandled request
    const { wrapper } = makeWrapper()
    const { result } = renderHook(() => useProductPrices(0), { wrapper })
    // Should never enter loading/success
    expect(result.current.isFetching).toBe(false)
    expect(result.current.data).toBeUndefined()
  })

  it('sends page and pageSize as query params in the URL', async () => {
    let capturedUrl: string | null = null
    server.use(
      http.get(`${API_URL}/api/v1/products/1/prices`, ({ request }) => {
        capturedUrl = request.url
        return HttpResponse.json(mockPagedResult)
      }),
    )
    const { wrapper } = makeWrapper()
    const { result } = renderHook(() => useProductPrices(1, 2, 10), { wrapper })
    await waitFor(() => expect(result.current.isSuccess).toBe(true))
    expect(capturedUrl).toContain('page=2')
    expect(capturedUrl).toContain('pageSize=10')
  })
})

describe('useAddProductPrice', () => {
  it('calls POST and invalidates product prices queries on success', async () => {
    server.use(
      http.get(`${API_URL}/api/v1/products/1/prices`, () => HttpResponse.json(mockPagedResult)),
      http.post(`${API_URL}/api/v1/admin/products/1/prices`, () =>
        HttpResponse.json(mockResponse, { status: 201 }),
      ),
    )
    const { qc, wrapper } = makeWrapper()
    const invalidateSpy = vi.spyOn(qc, 'invalidateQueries')

    const { result } = renderHook(() => useAddProductPrice(1), { wrapper })

    await act(async () => {
      result.current.mutate({ price: 700, priceValidFrom: '2026-05-01' })
    })
    await waitFor(() => expect(result.current.isSuccess).toBe(true))
    expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['product-prices', 1] })
  })

  it('returns error state on 409', async () => {
    server.use(
      http.post(`${API_URL}/api/v1/admin/products/1/prices`, () =>
        HttpResponse.json({ error: 'product_price_forward_only' }, { status: 409 }),
      ),
    )
    const { wrapper } = makeWrapper()
    const { result } = renderHook(() => useAddProductPrice(1), { wrapper })
    await act(async () => {
      result.current.mutate({ price: 100, priceValidFrom: '2026-04-19' })
    })
    await waitFor(() => expect(result.current.isError).toBe(true))
  })
})
