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

const review = (id: string): Review => ({
  id,
  productId: 'p1',
  userId: 'u1',
  purchaseId: 'cccccccc-cccc-4ccc-8ccc-cccccccccccc',
  vendorId: null,
  rating: 5,
  body: 'great',
  status: 'approved',
  vendorReply: null,
  createdAt: new Date('2026-06-25T10:00:00.000Z'),
  updatedAt: new Date('2026-06-25T10:00:00.000Z'),
})

const page = (ids: string[]): Page<Review> => ({
  items: ids.map(review),
  nextCursor: ids.length > 0 ? 'c1' : null,
})

function mk(client: Partial<ReviewsClient>) {
  const full: ReviewsClient = {
    listReviews: async () => page([]),
    getRatingAggregate: async () => ({
      productId: 'p1',
      avg: 0,
      count: 0,
      distribution: { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 },
    }),
    submitReview: async () => review('r0'),
    ...client,
  }
  const wrapper = ({ children }: { children: ReactNode }) => (
    <ReviewsProvider client={full}>{children}</ReviewsProvider>
  )
  return { wrapper, full }
}

describe('useReviewList', () => {
  it('fetches the page on mount', async () => {
    const listReviews = vi.fn().mockResolvedValue({
      items: [{ ...review('r1'), createdAt: '2026-06-25T10:00:00.000Z', updatedAt: '2026-06-25T10:00:00.000Z' }],
      nextCursor: null,
    })
    const { wrapper } = mk({ listReviews })
    const { result } = renderHook(() => useReviewList('p1'), { wrapper })
    await waitFor(() => expect(result.current.loading).toBe(false))
    expect(result.current.data?.items).toHaveLength(1)
    expect(listReviews).toHaveBeenCalledWith('p1', undefined)
  })

  it('captures a thrown error into error state', async () => {
    const { wrapper } = mk({ listReviews: async () => { throw new Error('boom') } })
    const { result } = renderHook(() => useReviewList('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 listReviews = vi.fn().mockResolvedValue({
      items: [{ ...review('r2'), createdAt: '2026-06-25T10:00:00.000Z', updatedAt: '2026-06-25T10:00:00.000Z' }],
      nextCursor: 'c2',
    })
    const { wrapper } = mk({ listReviews })
    const { result } = renderHook(() => useReviewList('p1', undefined, page(['r1'])), { wrapper })
    expect(result.current.loading).toBe(false)
    expect(result.current.data?.items).toHaveLength(1)
    expect(listReviews).not.toHaveBeenCalled()
    act(() => result.current.reload())
    await waitFor(() => expect(result.current.data?.items[0]?.id).toBe('r2'))
    expect(listReviews).toHaveBeenCalledTimes(1)
  })

  it('opts change refetches', async () => {
    const listReviews = vi.fn()
      .mockResolvedValueOnce({ items: [], nextCursor: null })
      .mockResolvedValueOnce({
        items: [{ ...review('r9'), createdAt: '2026-06-25T10:00:00.000Z', updatedAt: '2026-06-25T10:00:00.000Z' }],
        nextCursor: null,
      })
    const { wrapper } = mk({ listReviews })
    let cursor: string | undefined
    const { result, rerender } = renderHook(() => useReviewList('p1', { cursor }), { wrapper })
    await waitFor(() => expect(result.current.loading).toBe(false))
    cursor = 'c1'
    rerender()
    await waitFor(() => expect(result.current.data?.items).toHaveLength(1))
    expect(listReviews).toHaveBeenCalledTimes(2)
  })

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