import { act, renderHook } from '@testing-library/react'
import type { ReactNode } from 'react'
import { describe, expect, it, vi } from 'vitest'
import { PaymentUiContext, type PaymentConfirmResult, type PaymentUiContextValue } from './context'
import { PaymentConfirmError } from './errors'
import { useConfirmPayment } from './useConfirmPayment'

function wrap(value: PaymentUiContextValue) {
  return ({ children }: { children: ReactNode }) => (
    <PaymentUiContext.Provider value={value}>{children}</PaymentUiContext.Provider>
  )
}

describe('useConfirmPayment', () => {
  it('throws PaymentProviderError outside a provider', () => {
    expect(() => renderHook(() => useConfirmPayment())).toThrow(/provider/i)
  })

  it('happy path → status succeeded, returns { kind: succeeded }', async () => {
    const confirm = vi.fn(async (): Promise<PaymentConfirmResult> => ({ kind: 'succeeded' }))
    const { result } = renderHook(() => useConfirmPayment(), {
      wrapper: wrap({ ready: true, confirm, error: null }),
    })
    let res: PaymentConfirmResult | undefined
    await act(async () => {
      res = await result.current.confirm({ returnUrl: 'https://x/return' })
    })
    expect(res).toEqual({ kind: 'succeeded' })
    expect(result.current.status).toBe('succeeded')
  })

  it('declined → status error, returns { kind: error }, NO throw', async () => {
    const confirm = vi.fn(
      async (): Promise<PaymentConfirmResult> => ({ kind: 'error', error: new PaymentConfirmError('declined', 'card_declined') }),
    )
    const { result } = renderHook(() => useConfirmPayment(), {
      wrapper: wrap({ ready: true, confirm, error: null }),
    })
    let res: PaymentConfirmResult | undefined
    await act(async () => {
      res = await result.current.confirm({ returnUrl: 'https://x/return' })
    })
    expect(res?.kind).toBe('error')
    expect(result.current.status).toBe('error')
    expect(result.current.error?.code).toBe('card_declined')
  })

  it('double-submit guard: a second confirm while in-flight does NOT invoke the PSP confirm twice', async () => {
    let release!: () => void
    const gate = new Promise<void>((r) => (release = r))
    const confirm = vi.fn(async (): Promise<PaymentConfirmResult> => {
      await gate
      return { kind: 'succeeded' }
    })
    const { result } = renderHook(() => useConfirmPayment(), {
      wrapper: wrap({ ready: true, confirm, error: null }),
    })
    await act(async () => {
      void result.current.confirm({ returnUrl: 'https://x/return' }) // first, in-flight
      void result.current.confirm({ returnUrl: 'https://x/return' }) // second, must be ignored
      release()
    })
    expect(confirm).toHaveBeenCalledTimes(1)
  })

  it('confirm identity is stable across re-renders', () => {
    const value: PaymentUiContextValue = { ready: true, confirm: async () => ({ kind: 'succeeded' }), error: null }
    const { result, rerender } = renderHook(() => useConfirmPayment(), { wrapper: wrap(value) })
    const first = result.current.confirm
    rerender()
    expect(result.current.confirm).toBe(first)
  })
})