import { afterEach, describe, expect, it, vi } from 'vitest'

const originalEnv = { ...process.env }

async function importService(env: Record<string, string | undefined> = {}) {
  vi.resetModules()
  process.env = { ...originalEnv, ...env }
  return import('./paypalService')
}

describe('paypalService', () => {
  afterEach(() => {
    process.env = { ...originalEnv }
    vi.unstubAllGlobals()
    vi.resetModules()
  })

  it('reports unconfigured when client credentials are placeholders or missing', async () => {
    const service = await importService({ PAYPAL_CLIENT_ID: 'your-paypal-client-id', PAYPAL_CLIENT_SECRET: 'secret' })
    expect(service.isConfigured()).toBe(false)
  })

  it('creates an order after obtaining and caching an access token', async () => {
    const fetchMock = vi.fn()
      .mockResolvedValueOnce({
        ok: true,
        json: vi.fn().mockResolvedValue({ access_token: 'access_1', expires_in: 3600 }),
      })
      .mockResolvedValueOnce({
        ok: true,
        json: vi.fn().mockResolvedValue({
          id: 'paypal_order_1',
          status: 'CREATED',
          links: [{ rel: 'approve', href: 'https://paypal.test/approve' }],
        }),
      })
      .mockResolvedValueOnce({
        ok: true,
        json: vi.fn().mockResolvedValue({ id: 'paypal_order_2', status: 'CREATED', links: [] }),
      })
    vi.stubGlobal('fetch', fetchMock)

    const service = await importService({
      PAYPAL_BASE_URL: 'https://paypal.test',
      PAYPAL_CLIENT_ID: 'client_1',
      PAYPAL_CLIENT_SECRET: 'secret_1',
    })

    await expect(service.createOrder({
      amount: 12345,
      currency: 'MAD',
      orderId: 'reservation_1',
      description: 'Reservation deposit',
      returnUrl: 'https://app.test/return',
      cancelUrl: 'https://app.test/cancel',
    })).resolves.toEqual({
      orderId: 'paypal_order_1',
      approveUrl: 'https://paypal.test/approve',
      status: 'CREATED',
    })

    await service.createOrder({
      amount: 500,
      currency: 'MAD',
      orderId: 'reservation_2',
      description: 'Second payment',
      returnUrl: 'https://app.test/return',
      cancelUrl: 'https://app.test/cancel',
    })

    expect(fetchMock).toHaveBeenCalledTimes(3)
    expect(fetchMock).toHaveBeenNthCalledWith(1, 'https://paypal.test/v1/oauth2/token', expect.objectContaining({
      method: 'POST',
      body: 'grant_type=client_credentials',
      headers: expect.objectContaining({ Authorization: `Basic ${Buffer.from('client_1:secret_1').toString('base64')}` }),
    }))
    expect(fetchMock).toHaveBeenNthCalledWith(2, 'https://paypal.test/v2/checkout/orders', expect.objectContaining({
      method: 'POST',
      headers: expect.objectContaining({ Authorization: 'Bearer access_1' }),
      body: expect.stringContaining('"value":"123.45"'),
    }))
  })

  it('captures and refunds through authenticated PayPal endpoints with cent-to-decimal formatting', async () => {
    const fetchMock = vi.fn()
      .mockResolvedValueOnce({ ok: true, json: vi.fn().mockResolvedValue({ access_token: 'access_1', expires_in: 3600 }) })
      .mockResolvedValueOnce({ ok: true, json: vi.fn().mockResolvedValue({ id: 'capture_result' }) })
      .mockResolvedValueOnce({ ok: true, json: vi.fn().mockResolvedValue({ id: 'refund_result' }) })
    vi.stubGlobal('fetch', fetchMock)

    const service = await importService({ PAYPAL_BASE_URL: 'https://paypal.test', PAYPAL_CLIENT_ID: 'client_1', PAYPAL_CLIENT_SECRET: 'secret_1' })

    await expect(service.captureOrder('order_1')).resolves.toEqual({ id: 'capture_result' })
    await expect(service.refundCapture('capture_1', 2500, 'MAD', 'Customer cancellation')).resolves.toEqual({ id: 'refund_result' })

    expect(fetchMock).toHaveBeenNthCalledWith(2, 'https://paypal.test/v2/checkout/orders/order_1/capture', expect.objectContaining({
      method: 'POST',
      body: '{}',
    }))
    expect(fetchMock).toHaveBeenNthCalledWith(3, 'https://paypal.test/v2/payments/captures/capture_1/refund', expect.objectContaining({
      method: 'POST',
      body: JSON.stringify({ amount: { currency_code: 'MAD', value: '25.00' }, note_to_payer: 'Customer cancellation' }),
    }))
  })

  it('returns false instead of throwing when webhook verification cannot be completed', async () => {
    vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('network down')))
    const service = await importService({ PAYPAL_CLIENT_ID: 'client_1', PAYPAL_CLIENT_SECRET: 'secret_1' })

    await expect(service.verifyWebhookEvent({}, '{"id":"event_1"}')).resolves.toBe(false)
  })
})
