import { sql } from 'drizzle-orm'
import { describe, expect, it } from 'vitest'
import { createPgliteClient } from '../../db/src/postgres/pglite.js'
import { commit } from './commit.js'
import {
  isReservationConflictError,
  isReservationNotFoundError,
} from './errors.js'
import { pushSchema } from './migrate.js'
import { meteringSchema } from './schema.js'
import { release } from './release.js'

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

async function freshDb(
  reservationId: string,
  status: 'reserved' | 'committed' | 'released' | 'expired' = 'reserved',
) {
  const db = createPgliteClient({ schema: meteringSchema })
  await pushSchema(db)
  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, ${status === 'reserved' ? 5n : 0n})
  `)
  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 (
      ${reservationId}, 'tenant-1', ${`key-${reservationId}`}, 'fingerprint-1',
      'account-1', 'api.calls', 'period-1',
      ${PERIOD_START.toISOString()}::timestamptz,
      ${PERIOD_END.toISOString()}::timestamptz,
      5, NULL, ${status}, 10, NULL, 'deny', 'policy-1',
      '2026-07-16T12:00:00.000Z'::timestamptz
    )
  `)
  return db
}

function rows(result: unknown): Array<Record<string, unknown>> {
  return (Array.isArray(result) ? result : (result as { rows?: Array<Record<string, unknown>> }).rows) ?? []
}

async function readState(
  db: Awaited<ReturnType<typeof freshDb>>,
  reservationId: string,
) {
  const reservation = rows(await db.execute(sql`
    SELECT status, reserved_units FROM metering_reservation WHERE id = ${reservationId}
  `))[0]
  const counter = rows(await db.execute(sql`
    SELECT committed, reserved FROM metering_counter
    WHERE tenant_id = 'tenant-1' AND account = 'account-1'
      AND meter = 'api.calls' AND period_id = 'period-1'
  `))[0]
  const events = rows(await db.execute(sql`
    SELECT id FROM metering_usage_event WHERE reservation_id = ${reservationId}
  `))
  const asBigint = (row: Record<string, unknown> | undefined, fields: string[]) =>
    row && Object.fromEntries(
      Object.entries(row).map(([key, value]) =>
        fields.includes(key) ? [key, BigInt(String(value))] : [key, value],
      ),
    )
  return {
    reservation: asBigint(reservation, ['reserved_units']),
    counter: asBigint(counter, ['committed', 'reserved']),
    events,
  }
}

describe('release', () => {
  it('transitions reserved to released, refunds the hold, and emits no event', async () => {
    const db = await freshDb('release-1')

    await expect(release({ db }, 'release-1')).resolves.toBeUndefined()

    await expect(readState(db, 'release-1')).resolves.toEqual({
      reservation: { status: 'released', reserved_units: 5n },
      counter: { committed: 0n, reserved: 0n },
      events: [],
    })
  })

  it('is idempotent on a released reservation', async () => {
    const db = await freshDb('release-2')

    await release({ db }, 'release-2')
    const before = await readState(db, 'release-2')
    await release({ db }, 'release-2')

    expect(await readState(db, 'release-2')).toEqual(before)
  })

  it('rejects unknown and cross-terminal reservations with typed conflicts', async () => {
    const db = await freshDb('release-3')
    await expect(release({ db }, 'missing')).rejects.toSatisfy((error) =>
      isReservationNotFoundError(error),
    )

    await commit({ db }, 'release-3', 5n)
    await expect(release({ db }, 'release-3')).rejects.toSatisfy((error) =>
      isReservationConflictError(error),
    )

    const expiredDb = await freshDb('release-expired', 'expired')
    await expect(release({ db: expiredDb }, 'release-expired')).rejects.toSatisfy((error) =>
      isReservationConflictError(error),
    )
  })
})
