import { act, renderHook, waitFor } from '@testing-library/react'
import type { ReactNode } from 'react'
import { describe, expect, it, vi } from 'vitest'
import type { RatingAggregate } from '@platform-modules/commerce-reviews'
import { ReviewsProvider } from './ReviewsProvider.js'
import { useRatingAggregate } from './useRatingAggregate.js'
import type { ReviewsClient } from './client.js'

const aggregate = (count: number): RatingAggregate => ({
  productId: 'p1',
  avg: count > 0 ? 4 : 0,
  count,
  distribution: { 1: 0, 2: 0, 3: 0, 4: 0, 5: count },
})

const wireAggregate = (count: number) => ({
  productId: 'p1',
  avg: count > 0 ? 4 : 0,
  count,
  distribution: { 1: 0, 2: 0, 3: 0, 4: 0, 5: count },
})

function mk(client: Partial<ReviewsClient>) {
  const full: ReviewsClient = {
    listReviews: async () => ({ items: [], nextCursor: null }),
    getRatingAggregate: async () => wireAggregate(0),
    submitReview: async () => ({
      id: 'r1',
      productId: 'p1',
      userId: 'u1',
      vendorId: null,
      rating: 5,
      body: null,
      status: 'pending',
      vendorReply: null,
      createdAt: '2026-06-25T10:00:00.000Z',
      updatedAt: '2026-06-25T10:00:00.000Z',
    }),
    ...client,
  }
  const wrapper = ({ children }: { children: ReactNode }) => (
    <ReviewsProvider client={full}>{children}</ReviewsProvider>
  )
  return { wrapper }
}

describe('useRatingAggregate', () => {
  it('fetches the aggregate on mount', async () => {
    const getRatingAggregate = vi.fn().mockResolvedValue(wireAggregate(3))
    const { wrapper } = mk({ getRatingAggregate })
    const { result } = renderHook(() => useRatingAggregate('p1'), { wrapper })
    await waitFor(() => expect(result.current.loading).toBe(false))
    expect(result.current.data?.count).toBe(3)
    expect(getRatingAggregate).toHaveBeenCalledWith('p1')
  })

  it('captures a thrown error into error state', async () => {
    const { wrapper } = mk({ getRatingAggregate: async () => { throw new Error('boom') } })
    const { result } = renderHook(() => useRatingAggregate('p1'), { wrapper })
    await waitFor(() => expect(result.current.error?.message).toBe('boom'))
  })

  it('seeded: loading false, data from seed, NO mount fetch; reload() refetches', async () => {
    const getRatingAggregate = vi.fn().mockResolvedValue(wireAggregate(5))
    const { wrapper } = mk({ getRatingAggregate })
    const { result } = renderHook(() => useRatingAggregate('p1', aggregate(2)), { wrapper })
    expect(result.current.loading).toBe(false)
    expect(result.current.data?.count).toBe(2)
    expect(getRatingAggregate).not.toHaveBeenCalled()
    act(() => result.current.reload())
    await waitFor(() => expect(result.current.data?.count).toBe(5))
    expect(getRatingAggregate).toHaveBeenCalledTimes(1)
  })

  it('productId change refetches', async () => {
    const getRatingAggregate = vi.fn()
      .mockResolvedValueOnce(wireAggregate(1))
      .mockResolvedValueOnce(wireAggregate(4))
    const { wrapper } = mk({ getRatingAggregate })
    let productId = 'p1'
    const { result, rerender } = renderHook(() => useRatingAggregate(productId), { wrapper })
    await waitFor(() => expect(result.current.data?.count).toBe(1))
    productId = 'p2'
    rerender()
    await waitFor(() => expect(result.current.data?.count).toBe(4))
    expect(getRatingAggregate).toHaveBeenCalledTimes(2)
  })

  it('throws ReviewsProviderError outside a provider', () => {
    expect(() => renderHook(() => useRatingAggregate('p1'))).toThrow()
  })
})
