import { act, renderHook, waitFor } from '@testing-library/react'
import type { ReactNode } from 'react'
import { describe, expect, it, vi } from 'vitest'
import { MarketplaceProvider } from './MarketplaceProvider.js'
import { useUpdateVendorProduct } from './useUpdateVendorProduct.js'
import type { MarketplaceClient } from './client.js'

function deferred<T>() {
  let resolve!: (v: T) => void
  let reject!: (e: unknown) => void
  const promise = new Promise<T>((res, rej) => {
    resolve = res
    reject = rej
  })
  return { promise, resolve, reject }
}

const wireProduct = (over: Record<string, unknown> = {}) => ({
  id: 'p1',
  kind: 'physical',
  vendorId: 'v1',
  slug: 'tee',
  title: 'Tee',
  status: 'active',
  media: [{ key: 'm1', alt: 'a' }],
  tags: ['x'],
  availableFrom: '2026-01-01T00:00:00.000Z',
  availableUntil: null,
  createdAt: '2026-06-01T12:00:00.000Z',
  updatedAt: '2026-06-02T12:00:00.000Z',
  variants: [
    {
      id: 'v1',
      productId: 'p1',
      sku: 'S',
      attributes: {},
      prices: [{ currency: 'USD', amount: '1999', priceMode: 'exclusive' }],
    },
  ],
  ...over,
})

function wrap(client: MarketplaceClient) {
  return ({ children }: { children: ReactNode }) => (
    <MarketplaceProvider client={client}>{children}</MarketplaceProvider>
  )
}

function baseClient(overrides: Partial<MarketplaceClient> = {}): MarketplaceClient {
  return {
    getVendorProfile: vi.fn(),
    getVendorOrders: vi.fn(),
    applyAsVendor: vi.fn(),
    createVendorProduct: vi.fn(),
    updateVendorProduct: vi.fn(),
    ...overrides,
  }
}

const patch = { title: 'Updated Tee' }

describe('useUpdateVendorProduct', () => {
  it('update(productId, patch) sets revived result matching productId', async () => {
    const d = deferred<ReturnType<typeof wireProduct>>()
    const updateVendorProduct = vi.fn().mockReturnValue(d.promise)
    const client = baseClient({ updateVendorProduct })
    const { result } = renderHook(() => useUpdateVendorProduct(), { wrapper: wrap(client) })

    expect(result.current.pending).toBe(false)

    let updatePromise!: Promise<unknown>
    act(() => {
      updatePromise = result.current.update('p9', patch)
    })

    expect(result.current.pending).toBe(true)

    await act(async () => {
      d.resolve(wireProduct({ id: 'p9', title: 'Updated Tee' }))
      await updatePromise
    })

    expect(updateVendorProduct).toHaveBeenCalledWith('p9', patch)
    expect(result.current.result?.id).toBe('p9')
    expect(result.current.result?.title).toBe('Updated Tee')
    expect(result.current.result?.createdAt).toBeInstanceOf(Date)
    expect(result.current.error).toBeUndefined()
    await waitFor(() => expect(result.current.pending).toBe(false))
  })

  it('update rejects sets error', async () => {
    const err = new Error('validation failed')
    const updateVendorProduct = vi.fn().mockRejectedValue(err)
    const client = baseClient({ updateVendorProduct })
    const { result } = renderHook(() => useUpdateVendorProduct(), { wrapper: wrap(client) })

    await act(async () => {
      await expect(result.current.update('p1', patch)).rejects.toThrow('validation failed')
    })

    expect(result.current.error).toBeInstanceOf(Error)
    expect(result.current.error?.message).toBe('validation failed')
    expect(result.current.result).toBeUndefined()
  })

  it('reset clears result and error', async () => {
    const updateVendorProduct = vi.fn().mockResolvedValue(wireProduct())
    const client = baseClient({ updateVendorProduct })
    const { result } = renderHook(() => useUpdateVendorProduct(), { wrapper: wrap(client) })

    await act(async () => {
      await result.current.update('p1', patch)
    })

    act(() => result.current.reset())

    expect(result.current.result).toBeUndefined()
    expect(result.current.error).toBeUndefined()
  })

  it('pending is true while in flight', async () => {
    const d = deferred<ReturnType<typeof wireProduct>>()
    const updateVendorProduct = vi.fn().mockReturnValue(d.promise)
    const client = baseClient({ updateVendorProduct })
    const { result } = renderHook(() => useUpdateVendorProduct(), { wrapper: wrap(client) })

    let updatePromise!: Promise<unknown>
    act(() => {
      updatePromise = result.current.update('p1', patch)
    })

    expect(result.current.pending).toBe(true)

    await act(async () => {
      d.resolve(wireProduct({ title: 'Pending Tee' }))
      await updatePromise
    })

    await waitFor(() => expect(result.current.pending).toBe(false))
  })

  it('last-write-wins: rapid update of different products, second wins', async () => {
    const first = deferred<ReturnType<typeof wireProduct>>()
    const second = deferred<ReturnType<typeof wireProduct>>()
    const updateVendorProduct = vi
      .fn()
      .mockReturnValueOnce(first.promise)
      .mockReturnValueOnce(second.promise)
    const client = baseClient({ updateVendorProduct })
    const { result } = renderHook(() => useUpdateVendorProduct(), { wrapper: wrap(client) })

    let p1!: Promise<unknown>
    let p2!: Promise<unknown>
    act(() => {
      p1 = result.current.update('p-first', { title: 'First Tee' })
      p2 = result.current.update('p-second', { title: 'Second Tee' })
    })

    expect(result.current.pending).toBe(true)

    await act(async () => {
      second.resolve(wireProduct({ id: 'p-second', title: 'Second Tee' }))
      await p2
    })

    await waitFor(() => expect(result.current.result?.title).toBe('Second Tee'))
    expect(result.current.result?.id).toBe('p-second')

    await act(async () => {
      first.resolve(wireProduct({ id: 'p-first', title: 'First Tee' }))
      await p1
    })

    expect(result.current.result?.title).toBe('Second Tee')
    expect(result.current.result?.id).toBe('p-second')

    await waitFor(() => expect(result.current.pending).toBe(false))
  })

  it('resolve clears stale error from prior rejection', async () => {
    const updateVendorProduct = vi
      .fn()
      .mockRejectedValueOnce(new Error('validation failed'))
      .mockResolvedValueOnce(wireProduct())
    const client = baseClient({ updateVendorProduct })
    const { result } = renderHook(() => useUpdateVendorProduct(), { wrapper: wrap(client) })

    await act(async () => {
      await expect(result.current.update('p1', patch)).rejects.toThrow('validation failed')
    })
    expect(result.current.error).toBeInstanceOf(Error)

    await act(async () => {
      await result.current.update('p1', patch)
    })
    expect(result.current.error).toBeUndefined()
    expect(result.current.result?.id).toBe('p1')
  })

  it('reject clears stale result from prior success', async () => {
    const updateVendorProduct = vi
      .fn()
      .mockResolvedValueOnce(wireProduct({ id: 'p1' }))
      .mockRejectedValueOnce(new Error('fail'))
    const client = baseClient({ updateVendorProduct })
    const { result } = renderHook(() => useUpdateVendorProduct(), { wrapper: wrap(client) })

    await act(async () => {
      await result.current.update('p1', patch)
    })
    expect(result.current.result?.id).toBe('p1')

    await act(async () => {
      await expect(result.current.update('p1', patch)).rejects.toThrow('fail')
    })
    expect(result.current.result).toBeUndefined()
    expect(result.current.error).toBeInstanceOf(Error)
    expect(result.current.error?.message).toBe('fail')
  })

  it('reset() while in-flight orphans the late resolve', async () => {
    const d = deferred<ReturnType<typeof wireProduct>>()
    const updateVendorProduct = vi.fn().mockReturnValue(d.promise)
    const client = baseClient({ updateVendorProduct })
    const { result } = renderHook(() => useUpdateVendorProduct(), { wrapper: wrap(client) })

    let updatePromise!: Promise<unknown>
    act(() => {
      updatePromise = result.current.update('p1', patch)
    })

    act(() => result.current.reset())

    await act(async () => {
      d.resolve(wireProduct())
      await updatePromise
    })

    expect(result.current.result).toBeUndefined()
    await waitFor(() => expect(result.current.pending).toBe(false))
  })
})
