import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { createPayment, getPayment, buildReceipt, validateWebhook } from '../yookassa.js'

describe('yookassa createPayment', () => {
  beforeEach(() => {
    process.env.YOOKASSA_SHOP_ID = '123456'
    process.env.YOOKASSA_SECRET_KEY = 'test_secret'
    vi.stubGlobal('fetch', vi.fn())
  })

  afterEach(() => {
    vi.unstubAllGlobals()
    delete process.env.YOOKASSA_SHOP_ID
    delete process.env.YOOKASSA_SECRET_KEY
  })

  it('calls POST /payments with Basic auth and Idempotence-Key', async () => {
    const mockPayment = {
      id: '2d0c6f35-000f-5000-8000-1234567890ab',
      status: 'pending',
      paid: false,
      amount: { value: '1000.00', currency: 'RUB' },
      confirmation: { type: 'redirect', confirmation_url: 'https://yoomoney.ru/checkout/...' },
      created_at: '2026-05-20T12:00:00.000Z',
      test: true,
      refundable: false,
      recipient: { account_id: '123456', gateway_id: '123456' },
    }
    fetch.mockResolvedValue({
      ok: true,
      status: 200,
      json: () => Promise.resolve(mockPayment),
    })

    const result = await createPayment({
      amount: { value: '1000.00', currency: 'RUB' },
      description: 'Order #test',
      receipt: {
        customer: { email: 'test@example.com' },
        items: [{ description: 'Item', quantity: 1, amount: { value: '1000.00', currency: 'RUB' }, vat_code: 1 }],
        tax_system_code: 1,
      },
      confirmation: { type: 'redirect', return_url: 'http://localhost:5173/me/orders/test?paid=1' },
      metadata: { orderId: 'test' },
      idempotencyKey: 'test-v1',
    })

    expect(fetch).toHaveBeenCalledTimes(1)
    const [url, opts] = fetch.mock.calls[0]
    expect(url).toBe('https://api.yookassa.ru/v3/payments')
    expect(opts.method).toBe('POST')
    expect(opts.headers['Idempotence-Key']).toBe('test-v1')
    expect(opts.headers['Authorization']).toBe('Basic MTIzNDU2OnRlc3Rfc2VjcmV0')
    expect(result.paymentId).toBe('2d0c6f35-000f-5000-8000-1234567890ab')
    expect(result.confirmationUrl).toBe('https://yoomoney.ru/checkout/...')
    expect(result.status).toBe('pending')
  })

  it('retries on 5xx error', async () => {
    fetch
      .mockResolvedValueOnce({ ok: false, status: 500 })
      .mockResolvedValueOnce({ ok: false, status: 503 })
      .mockResolvedValueOnce({
        ok: true,
        status: 200,
        json: () =>
          Promise.resolve({
            id: 'retry-id',
            status: 'pending',
            paid: false,
            amount: { value: '500.00', currency: 'RUB' },
            confirmation: { type: 'redirect', confirmation_url: 'https://yoomoney.ru/checkout/retry' },
            created_at: '2026-05-20T12:00:00.000Z',
            test: true,
            refundable: false,
            recipient: { account_id: '123456', gateway_id: '123456' },
          }),
      })

    const result = await createPayment({
      amount: { value: '500.00', currency: 'RUB' },
      description: 'Retry test',
      receipt: {
        customer: { email: 'test@example.com' },
        items: [{ description: 'Item', quantity: 1, amount: { value: '500.00', currency: 'RUB' }, vat_code: 1 }],
        tax_system_code: 1,
      },
      confirmation: { type: 'redirect', return_url: 'http://localhost:5173/me/orders/test' },
      metadata: {},
      idempotencyKey: 'retry-v1',
    })

    expect(fetch).toHaveBeenCalledTimes(3)
    expect(result.paymentId).toBe('retry-id')
  })

  it('throws on 4xx error', async () => {
    fetch.mockResolvedValue({
      ok: false,
      status: 400,
      json: () =>
        Promise.resolve({
          type: 'error',
          id: 'err-id',
          code: 'invalid_request',
          description: 'Missing required field',
        }),
    })

    await expect(
      createPayment({
        amount: { value: '1000.00', currency: 'RUB' },
        description: 'Bad request',
        receipt: {
          customer: { email: 'test@example.com' },
          items: [{ description: 'Item', quantity: 1, amount: { value: '1000.00', currency: 'RUB' }, vat_code: 1 }],
          tax_system_code: 1,
        },
        confirmation: { type: 'redirect', return_url: 'http://localhost:5173' },
        metadata: {},
        idempotencyKey: 'bad-v1',
      }),
    ).rejects.toThrow('YooKassa API error')
  })
})

describe('yookassa getPayment', () => {
  beforeEach(() => {
    process.env.YOOKASSA_SHOP_ID = '123456'
    process.env.YOOKASSA_SECRET_KEY = 'test_secret'
    vi.stubGlobal('fetch', vi.fn())
  })

  afterEach(() => {
    vi.unstubAllGlobals()
    delete process.env.YOOKASSA_SHOP_ID
    delete process.env.YOOKASSA_SECRET_KEY
  })

  it('calls GET /payments/{id} and returns payment data', async () => {
    fetch.mockResolvedValue({
      ok: true,
      status: 200,
      json: () =>
        Promise.resolve({
          id: 'payment-id',
          status: 'succeeded',
          paid: true,
          amount: { value: '1000.00', currency: 'RUB' },
          created_at: '2026-05-20T12:00:00.000Z',
          test: true,
          refundable: true,
          recipient: { account_id: '123456', gateway_id: '123456' },
        }),
    })

    const result = await getPayment('payment-id')
    expect(fetch).toHaveBeenCalledTimes(1)
    expect(fetch.mock.calls[0][0]).toBe('https://api.yookassa.ru/v3/payments/payment-id')
    expect(result.paymentId).toBe('payment-id')
    expect(result.status).toBe('succeeded')
    expect(result.paid).toBe(true)
  })
})

describe('yookassa buildReceipt', () => {
  it('builds receipt with order items', () => {
    const result = buildReceipt({
      orderItems: [{ titleSnapshot: 'Test Product', qty: 2, priceCentsSnapshot: 100000 }],
      deliveryFeeCents: 0,
      userEmail: 'user@test.ru',
    })

    expect(result.customer.email).toBe('user@test.ru')
    expect(result.items).toHaveLength(1)
    expect(result.items[0].description).toBe('Test Product')
    expect(result.items[0].quantity).toBe(2)
    expect(result.items[0].amount.value).toBe('1000.00')
    expect(result.items[0].vat_code).toBe(1)
    expect(result.items[0].measure).toBe('piece')
    expect(result.items[0].payment_subject).toBe('commodity')
    expect(result.items[0].payment_mode).toBe('full_prepayment')
    expect(result.tax_system_code).toBe(1)
  })

  it('adds delivery item when deliveryFeeCents > 0', () => {
    const result = buildReceipt({
      orderItems: [{ titleSnapshot: 'Item A', qty: 1, priceCentsSnapshot: 50000 }],
      deliveryFeeCents: 35000,
      userEmail: 'user@test.ru',
    })

    expect(result.items).toHaveLength(2)
    expect(result.items[1].description).toBe('Доставка')
    expect(result.items[1].amount.value).toBe('350.00')
    expect(result.items[1].payment_subject).toBe('service')
  })

  it('passes through taxSystemCode', () => {
    const result = buildReceipt({
      orderItems: [{ titleSnapshot: 'Item', qty: 1, priceCentsSnapshot: 1000 }],
      deliveryFeeCents: 0,
      userEmail: 'user@test.ru',
      taxSystemCode: 3,
    })

    expect(result.tax_system_code).toBe(3)
  })
})

describe('yookassa validateWebhook', () => {
  beforeEach(() => {
    process.env.YOOKASSA_SECRET_KEY = 'test_secret'
  })

  afterEach(() => {
    delete process.env.YOOKASSA_SECRET_KEY
  })

  it('returns event and paymentObject for valid notification', () => {
    const body = {
      type: 'notification',
      event: 'payment.succeeded',
      object: { id: 'yk-id', status: 'succeeded', paid: true },
    }
    const result = validateWebhook('127.0.0.1', body)
    expect(result.event).toBe('payment.succeeded')
    expect(result.paymentObject.id).toBe('yk-id')
  })

  it('throws if type is not notification', () => {
    expect(() => validateWebhook('127.0.0.1', { type: 'other', event: 'x', object: {} })).toThrow(
      'Expected notification type',
    )
  })

  it('throws if missing event', () => {
    expect(() => validateWebhook('127.0.0.1', { type: 'notification', object: {} })).toThrow('Missing event or object')
  })

  it('throws if missing object', () => {
    expect(() => validateWebhook('127.0.0.1', { type: 'notification', event: 'x' })).toThrow('Missing event or object')
  })

  it('throws for invalid body type', () => {
    expect(() => validateWebhook('127.0.0.1', 'not an object')).toThrow('Invalid webhook body')
  })

  it('throws for null body', () => {
    expect(() => validateWebhook('127.0.0.1', null)).toThrow('Invalid webhook body')
  })

  it('skips IP validation in test mode (test_ key)', () => {
    const body = { type: 'notification', event: 'payment.succeeded', object: {} }
    expect(() => validateWebhook('1.2.3.4', body)).not.toThrow()
  })
})
