import { describe, expect, it } from 'vitest'
import type { ProviderEvent } from '../index.js'
import { idempotencyKey } from '../index.js'
import { buildChargeKey, ingestWebhook } from './index.js'
import {
  asLedgerSeam,
  createSubscriptionsTestDb,
  createTestSubscriptionProvider,
  listLedgerEntries,
} from './test-fixture.js'

describe('billing/subscriptions', () => {
  it('createSubscription is idempotent on caller idempotency key', async () => {
    const provider = createTestSubscriptionProvider()

    const first = await provider.createSubscription({
      idempotencyKey: 'sub-create-1',
      planId: 'plan_pro',
      currentPeriod: '2026-07',
    })
    const second = await provider.createSubscription({
      idempotencyKey: 'sub-create-1',
      planId: 'plan_pro',
      currentPeriod: '2026-07',
    })

    expect(second).toEqual(first)
    expect(provider.uniqueCreateCount).toBe(1)
  })

  it('settlement webhook returns a stable chargeKey of subscriptionId:period', async () => {
    const provider = createTestSubscriptionProvider()
    const db = await createSubscriptionsTestDb()

    const event = await ingestWebhook(
      {
        provider,
        ledger: asLedgerSeam(),
        db,
      },
      {
        type: 'subscription.settlement',
        subscriptionId: 'sub_1',
        period: '2026-07',
        amountMinor: 8000n,
        currency: 'USD',
      },
    )

    expect(event).toMatchObject({
      kind: 'settlement',
      chargeKey: 'sub_1:2026-07',
      subscriptionId: 'sub_1',
      period: '2026-07',
      amountMinor: 8000n,
    })
  })

  it('buildChargeKey throws when a component is empty or contains a colon', () => {
    expect(() => buildChargeKey('', '2026-07')).toThrowError(/subscriptionId/)
    expect(() => buildChargeKey('sub_1', '2026:07')).toThrowError(/period/)
  })

  it('posts one ledger entry when the same settlement webhook is ingested twice', async () => {
    const provider = createTestSubscriptionProvider()
    const db = await createSubscriptionsTestDb()
    const deps = {
      provider,
      ledger: asLedgerSeam(),
      db,
    }
    const rawEvent = {
      type: 'subscription.settlement' as const,
      subscriptionId: 'sub_repeat',
      period: '2026-07',
      amountMinor: 9900n,
      currency: 'USD',
      eventId: 'evt_sub_repeat',
    }

    await ingestWebhook(deps, rawEvent)
    await ingestWebhook(deps, rawEvent)

    const rows = await listLedgerEntries(db)
    expect(rows).toHaveLength(1)
    expect(rows[0]?.idempotencyKey).toBe(idempotencyKey(['charge', 'sub_repeat:2026-07']))
    expect(rows[0]?.delta).toBe(9900n)
  })

  it('rejects subscription webhook amountMinor values with too many digits', async () => {
    const provider = createTestSubscriptionProvider()
    const db = await createSubscriptionsTestDb()

    await expect(
      ingestWebhook(
        {
          provider,
          ledger: asLedgerSeam(),
          db,
        },
        {
          type: 'subscription.settlement',
          subscriptionId: 'sub_huge',
          period: '2026-07',
          amountMinor: BigInt('1'.repeat(22)),
          currency: 'USD',
        },
      ),
    ).rejects.toThrow(/too many digits/i)
  })

  it('rejects subscription statuses outside the allowlist', async () => {
    const provider = createTestSubscriptionProvider()

    await expect(
      provider.createSubscription({
        idempotencyKey: 'sub-create-bad-status',
        planId: 'plan_pro',
        currentPeriod: '2026-07',
        status: 'surprise',
      }),
    ).rejects.toThrow(/status/i)
  })

  it('ProviderEvent typechecks for subscription settlement payloads', () => {
    const event: ProviderEvent = {
      kind: 'settlement',
      eventId: 'evt_sub_typecheck',
      chargeKey: 'sub_typecheck:2026-07',
      providerRef: 'sub_typecheck',
      amount: 1500,
      currency: 'USD',
      subscriptionId: 'sub_typecheck',
      period: '2026-07',
      amountMinor: 1500n,
    }

    expect(event.amountMinor).toBe(1500n)
  })
})
