import { sql } from 'drizzle-orm'
import { describe, expect, it, vi } from 'vitest'
import { createPgliteClient } from '../../db/src/postgres/pglite.js'
import { pushSchema } from './migrate.js'
import { meteringSchema } from './schema.js'
import { usage } from './usage.js'
import { isEntitlementDeniedError, isMeteringStorageError } from './errors.js'
import type { EntitlementPolicy, SubjectRef, UsageQuery } from './types.js'

const AT = new Date('2026-07-14T12:00:00.000Z')
const PERIOD = {
  id: 'period-1',
  startsAt: new Date('2026-07-01T00:00:00.000Z'),
  endsAt: new Date('2026-08-01T00:00:00.000Z'),
}
const SUBJECT: SubjectRef = { tenantId: 'tenant-1', account: 'account-1' }

type Database = Awaited<ReturnType<typeof database>>

function policy(overrides: Partial<EntitlementPolicy> = {}): EntitlementPolicy {
  return {
    meter: 'api.calls',
    includedUnits: 10n,
    overage: 'deny',
    period: PERIOD,
    version: 'policy-1',
    ...overrides,
  }
}

async function database() {
  const db = createPgliteClient({ schema: meteringSchema })
  await pushSchema(db)
  return db
}

async function seedProjection(db: Database, appliedPolicy: EntitlementPolicy) {
  await db.execute(sql`
    INSERT INTO metering_counter (
      tenant_id, account, meter, period_id, committed, reserved
    ) VALUES (${SUBJECT.tenantId}, ${SUBJECT.account}, ${appliedPolicy.meter},
      ${appliedPolicy.period.id}, 999, 999)
  `)
  await db.execute(sql`
    INSERT INTO metering_reservation (
      id, tenant_id, idempotency_key, fingerprint, account, meter, period_id,
      period_starts_at, period_ends_at, reserved_units, committed_units, status,
      applied_included_units, applied_hard_limit, applied_overage, policy_version,
      expires_at
    ) VALUES (
      'committed-1', ${SUBJECT.tenantId}, 'key-committed', 'fingerprint-committed',
      ${SUBJECT.account}, ${appliedPolicy.meter}, ${appliedPolicy.period.id},
      ${appliedPolicy.period.startsAt.toISOString()}::timestamptz,
      ${appliedPolicy.period.endsAt.toISOString()}::timestamptz,
      4, 4, 'committed', ${appliedPolicy.includedUnits},
      ${appliedPolicy.hardLimit ?? null}, ${appliedPolicy.overage},
      ${appliedPolicy.version}, '2026-07-15T12:00:00Z'::timestamptz
    )
  `)
  await db.execute(sql`
    INSERT INTO metering_reservation (
      id, tenant_id, idempotency_key, fingerprint, account, meter, period_id,
      period_starts_at, period_ends_at, reserved_units, committed_units, status,
      applied_included_units, applied_hard_limit, applied_overage, policy_version,
      expires_at
    ) VALUES (
      'reserved-1', ${SUBJECT.tenantId}, 'key-reserved', 'fingerprint-reserved',
      ${SUBJECT.account}, ${appliedPolicy.meter}, ${appliedPolicy.period.id},
      ${appliedPolicy.period.startsAt.toISOString()}::timestamptz,
      ${appliedPolicy.period.endsAt.toISOString()}::timestamptz,
      3, NULL, 'reserved', ${appliedPolicy.includedUnits},
      ${appliedPolicy.hardLimit ?? null}, ${appliedPolicy.overage},
      ${appliedPolicy.version}, '2026-07-15T12:00:00Z'::timestamptz
    )
  `)
  await db.execute(sql`
    INSERT INTO metering_usage_event (
      id, tenant_id, account, meter, period_id, units, kind, reservation_id,
      rule_version
    ) VALUES (
      'committed-1:commit', ${SUBJECT.tenantId}, ${SUBJECT.account},
      ${appliedPolicy.meter}, ${appliedPolicy.period.id}, 4, 'commit',
      'committed-1', NULL
    )
  `)
}

describe('usage', () => {
  it.each([
    {
      name: 'deny caps capacity at included units',
      currentPolicy: policy({ hardLimit: 100n }),
      expected: { includedRemaining: 3n, capacityRemaining: 3n },
    },
    {
      name: 'allow caps capacity at hard limit',
      currentPolicy: policy({ overage: 'allow', hardLimit: 20n }),
      expected: { includedRemaining: 3n, capacityRemaining: 13n },
    },
    {
      name: 'allow without hard limit is unlimited',
      currentPolicy: policy({ overage: 'allow' }),
      expected: { includedRemaining: 3n, capacityRemaining: null },
    },
  ])('returns split remaining values: $name', async ({ currentPolicy, expected }) => {
    const db = await database()
    await seedProjection(db, currentPolicy)
    const policies = { resolve: vi.fn().mockResolvedValue(currentPolicy) }

    await expect(
      usage({ db, policies }, { subject: SUBJECT, meter: currentPolicy.meter, at: AT }),
    ).resolves.toMatchObject({
      period: PERIOD,
      committed: 4n,
      reserved: 3n,
      ...expected,
    })
    expect(policies.resolve).toHaveBeenCalledWith(SUBJECT, currentPolicy.meter, AT)
  })

  it('uses the pinned policy snapshot for historical periods', async () => {
    const db = await database()
    const appliedPolicy = policy({ overage: 'allow', hardLimit: 20n })
    await seedProjection(db, appliedPolicy)
    const policies = { resolve: vi.fn() }

    await expect(
      usage({ db, policies }, { subject: SUBJECT, meter: appliedPolicy.meter, periodId: PERIOD.id }),
    ).resolves.toMatchObject({
      period: PERIOD,
      committed: 4n,
      reserved: 3n,
      includedRemaining: 3n,
      capacityRemaining: 13n,
    })
    expect(policies.resolve).not.toHaveBeenCalled()
  })

  it('denies current usage when policy resolution returns null', async () => {
    const db = await database()
    const policies = { resolve: vi.fn().mockResolvedValue(null) }

    await expect(
      usage({ db, policies }, { subject: SUBJECT, meter: 'api.calls', at: AT }),
    ).rejects.toSatisfy((error) => isEntitlementDeniedError(error))
  })

  it('rejects a query that supplies both current and historical selectors', async () => {
    const db = await database()
    const policies = { resolve: vi.fn() }
    const query = {
      subject: SUBJECT,
      meter: 'api.calls',
      at: AT,
      periodId: PERIOD.id,
    } as UsageQuery

    await expect(usage({ db, policies }, query)).rejects.toMatchObject({
      name: 'MeteringValidationError',
    })
    expect(policies.resolve).not.toHaveBeenCalled()
  })

  it('rejects oversized stored usage quantities', async () => {
    const db = await database()
    await db.execute(sql`
      INSERT INTO metering_counter (
        tenant_id, account, meter, period_id, committed, reserved
      ) VALUES (${SUBJECT.tenantId}, ${SUBJECT.account}, 'api.calls', ${PERIOD.id}, 0, 0)
    `)
    await db.execute(
      sql`ALTER TABLE metering_usage_event DROP CONSTRAINT metering_usage_event_units_bounded`,
    )
    await db.execute(sql`
      INSERT INTO metering_usage_event (
        id, tenant_id, account, meter, period_id, units, kind, reservation_id
      ) VALUES (
        'oversized:commit', ${SUBJECT.tenantId}, ${SUBJECT.account},
        'api.calls', ${PERIOD.id}, ${10n ** 21n}, 'commit', 'oversized'
      )
    `)
    const currentPolicy = policy()
    const policies = { resolve: vi.fn().mockResolvedValue(currentPolicy) }

    await expect(
      usage({ db, policies }, { subject: SUBJECT, meter: currentPolicy.meter, at: AT }),
    ).rejects.toSatisfy((error) => {
      const cause = (error as { cause?: unknown }).cause
      return (
        isMeteringStorageError(error) &&
        cause instanceof Error &&
        /too many digits/.test(cause.message)
      )
    })
  })
})
