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 { useCreateVendorProduct } from './useCreateVendorProduct.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 createInput = {
  kind: 'physical' as const,
  slug: 'new-tee',
  title: 'New Tee',
}

describe('useCreateVendorProduct', () => {
  it('successful create sets revived result; pending false → true → false', async () => {
    const d = deferred<ReturnType<typeof wireProduct>>()
    const createVendorProduct = vi.fn().mockReturnValue(d.promise)
    const client = baseClient({ createVendorProduct })
    const { result } = renderHook(() => useCreateVendorProduct(), { wrapper: wrap(client) })

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

    let createPromise!: Promise<unknown>
    act(() => {
      createPromise = result.current.create(createInput)
    })

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

    await act(async () => {
      d.resolve(wireProduct({ id: 'p9', title: 'New Tee', slug: 'new-tee' }))
      await createPromise
    })

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

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

    await act(async () => {
      await expect(result.current.create(createInput)).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 createVendorProduct = vi.fn().mockResolvedValue(wireProduct())
    const client = baseClient({ createVendorProduct })
    const { result } = renderHook(() => useCreateVendorProduct(), { wrapper: wrap(client) })

    await act(async () => {
      await result.current.create(createInput)
    })

    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 createVendorProduct = vi.fn().mockReturnValue(d.promise)
    const client = baseClient({ createVendorProduct })
    const { result } = renderHook(() => useCreateVendorProduct(), { wrapper: wrap(client) })

    let createPromise!: Promise<unknown>
    act(() => {
      createPromise = result.current.create(createInput)
    })

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

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

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

  it('last-write-wins: two rapid creates, second resolve wins', async () => {
    const first = deferred<ReturnType<typeof wireProduct>>()
    const second = deferred<ReturnType<typeof wireProduct>>()
    const createVendorProduct = vi
      .fn()
      .mockReturnValueOnce(first.promise)
      .mockReturnValueOnce(second.promise)
    const client = baseClient({ createVendorProduct })
    const { result } = renderHook(() => useCreateVendorProduct(), { wrapper: wrap(client) })

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

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

    await act(async () => {
      second.resolve(wireProduct({ id: 'p-second', title: 'Second Tee', slug: 'second' }))
      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', slug: 'first' }))
      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 createVendorProduct = vi
      .fn()
      .mockRejectedValueOnce(new Error('validation failed'))
      .mockResolvedValueOnce(wireProduct())
    const client = baseClient({ createVendorProduct })
    const { result } = renderHook(() => useCreateVendorProduct(), { wrapper: wrap(client) })

    await act(async () => {
      await expect(result.current.create(createInput)).rejects.toThrow('validation failed')
    })
    expect(result.current.error).toBeInstanceOf(Error)

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

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

    await act(async () => {
      await result.current.create(createInput)
    })
    expect(result.current.result?.id).toBe('p1')

    await act(async () => {
      await expect(result.current.create(createInput)).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 createVendorProduct = vi.fn().mockReturnValue(d.promise)
    const client = baseClient({ createVendorProduct })
    const { result } = renderHook(() => useCreateVendorProduct(), { wrapper: wrap(client) })

    let createPromise!: Promise<unknown>
    act(() => {
      createPromise = result.current.create(createInput)
    })

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

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

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