import { sql } from 'drizzle-orm'
import { describe, expect, it } from 'vitest'
import { createPgliteClient } from '../../db/src/postgres/pglite.js'
import { expireStale } from './sweep.js'
import { pushSchema } from './migrate.js'
import { meteringSchema } from './schema.js'
import { isMeteringStorageError } from './errors.js'

const BEFORE = new Date('2026-07-14T12:30:00.000Z')
const PERIOD_START = new Date('2026-07-01T00:00:00.000Z')
const PERIOD_END = new Date('2026-08-01T00:00:00.000Z')

type Database = Awaited<ReturnType<typeof database>>

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

async function seedReservation(
  db: Database,
  input: {
    id: string
    reservedUnits: bigint
    expiresAt: Date
  },
) {
  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 (
      ${input.id}, 'tenant-1', ${`key-${input.id}`}, ${`fingerprint-${input.id}`},
      'account-1', 'api.calls', 'period-1',
      ${PERIOD_START.toISOString()}::timestamptz,
      ${PERIOD_END.toISOString()}::timestamptz,
      ${input.reservedUnits}, NULL, 'reserved', 100, NULL, 'deny', 'policy-1',
      ${input.expiresAt.toISOString()}::timestamptz
    )
  `)
}

async function readState(db: Database) {
  const reservations = await db.execute(sql`
    SELECT id, status, reserved_units
    FROM metering_reservation
    ORDER BY id
  `)
  const counter = await db.execute(sql`
    SELECT reserved
    FROM metering_counter
    WHERE tenant_id = 'tenant-1'
      AND account = 'account-1'
      AND meter = 'api.calls'
      AND period_id = 'period-1'
  `)
  return {
    reservations: reservations.rows.map((row) => ({
      id: row.id,
      status: row.status,
      reservedUnits: BigInt(String(row.reserved_units)),
    })),
    reserved: BigInt(String(counter.rows[0]?.reserved)),
  }
}

describe('expireStale', () => {
  it('expires stale rows up to limit, refunds holds, and leaves boundary rows active', async () => {
    const db = await database()
    await db.execute(sql`
      INSERT INTO metering_counter (
        tenant_id, account, meter, period_id, committed, reserved
      ) VALUES ('tenant-1', 'account-1', 'api.calls', 'period-1', 0, 14)
    `)
    await seedReservation(db, {
      id: 'stale-1',
      reservedUnits: 2n,
      expiresAt: new Date('2026-07-14T12:10:00.000Z'),
    })
    await seedReservation(db, {
      id: 'stale-2',
      reservedUnits: 3n,
      expiresAt: new Date('2026-07-14T12:20:00.000Z'),
    })
    await seedReservation(db, {
      id: 'boundary',
      reservedUnits: 4n,
      expiresAt: BEFORE,
    })
    await seedReservation(db, {
      id: 'active',
      reservedUnits: 5n,
      expiresAt: new Date('2026-07-14T13:00:00.000Z'),
    })

    await expect(expireStale({ db }, BEFORE, 1)).resolves.toBe(1)
    expect((await readState(db)).reserved).toBe(12n)

    await expect(expireStale({ db }, BEFORE, 10)).resolves.toBe(1)
    await expect(expireStale({ db }, BEFORE, 10)).resolves.toBe(0)
    await expect(readState(db)).resolves.toEqual({
      reserved: 9n,
      reservations: [
        { id: 'active', status: 'reserved', reservedUnits: 5n },
        { id: 'boundary', status: 'reserved', reservedUnits: 4n },
        { id: 'stale-1', status: 'expired', reservedUnits: 2n },
        { id: 'stale-2', status: 'expired', reservedUnits: 3n },
      ],
    })
  })

  it('rejects invalid sweep bounds before touching storage', async () => {
    const db = await database()

    await expect(expireStale({ db }, new Date('invalid'), 1)).rejects.toMatchObject({
      name: 'MeteringValidationError',
    })
    await expect(expireStale({ db }, BEFORE, -1)).rejects.toMatchObject({
      name: 'MeteringValidationError',
    })
  })

  it('rejects oversized stored reservation quantities', async () => {
    const db = await database()
    await db.execute(sql`
      INSERT INTO metering_counter (
        tenant_id, account, meter, period_id, committed, reserved
      ) VALUES ('tenant-1', 'account-1', 'api.calls', 'period-1', 0, 0)
    `)
    await db.execute(
      sql`ALTER TABLE metering_reservation DROP CONSTRAINT metering_reservation_reserved_units_bounded`,
    )
    await seedReservation(db, {
      id: 'oversized',
      reservedUnits: 10n ** 21n,
      expiresAt: new Date('2026-07-14T12:10:00.000Z'),
    })

    await expect(expireStale({ db }, BEFORE, 1)).rejects.toSatisfy((error) => {
      const cause = (error as { cause?: unknown }).cause
      return (
        isMeteringStorageError(error) &&
        cause instanceof Error &&
        /too many digits/.test(cause.message)
      )
    })
  })
})
