import { sql } from 'drizzle-orm'
import { createPgliteClient } from '@platform-modules/db/pglite'
import { describe, expect, it } from 'vitest'
import { isMeteringMigrateError, pushSchema } from './migrate.js'
import {
  meteringCounter,
  meteringReservation,
  meteringSchema,
  meteringUsageEvent,
} from './schema.js'

function tableName(table: unknown): string {
  return (table as Record<PropertyKey, unknown>)[Symbol.for('drizzle:Name')] as string
}

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

describe('metering schema', () => {
  it('exports the three tenant-keyed tables with the contract columns', () => {
    expect(tableName(meteringCounter)).toBe('metering_counter')
    expect(tableName(meteringReservation)).toBe('metering_reservation')
    expect(tableName(meteringUsageEvent)).toBe('metering_usage_event')
    expect(meteringSchema).toEqual({ meteringCounter, meteringReservation, meteringUsageEvent })

    expect(Object.keys(meteringCounter)).toEqual(
      expect.arrayContaining(['tenantId', 'account', 'meter', 'periodId', 'committed', 'reserved']),
    )
    expect(Object.keys(meteringReservation)).toEqual(
      expect.arrayContaining([
        'id',
        'tenantId',
        'idempotencyKey',
        'fingerprint',
        'account',
        'meter',
        'periodId',
        'periodStartsAt',
        'periodEndsAt',
        'reservedUnits',
        'committedUnits',
        'status',
        'appliedIncludedUnits',
        'appliedHardLimit',
        'appliedOverage',
        'policyVersion',
        'expiresAt',
        'createdAt',
      ]),
    )
    expect(Object.keys(meteringUsageEvent)).toEqual(
      expect.arrayContaining([
        'id',
        'tenantId',
        'account',
        'meter',
        'periodId',
        'units',
        'kind',
        'reservationId',
        'ruleVersion',
        'createdAt',
      ]),
    )
  })

  it('provisions constraints and remains idempotent on repeated push', async () => {
    const db = createPgliteClient({ schema: meteringSchema })

    await pushSchema(db)
    await pushSchema(db)

    const tables = rows(await db.execute(sql`
      SELECT table_name
      FROM information_schema.tables
      WHERE table_schema = 'public'
        AND table_name IN ('metering_counter', 'metering_reservation', 'metering_usage_event')
      ORDER BY table_name
    `))
    expect(tables.map((row) => row.table_name)).toEqual([
      'metering_counter',
      'metering_reservation',
      'metering_usage_event',
    ])

    const constraints = rows(await db.execute(sql`
      SELECT conname, contype
      FROM pg_constraint
      WHERE conrelid IN (
        'metering_counter'::regclass,
        'metering_reservation'::regclass,
        'metering_usage_event'::regclass
      )
      ORDER BY conname
    `))
    expect(constraints).toEqual(
      expect.arrayContaining([
        expect.objectContaining({ conname: 'metering_counter_pkey', contype: 'p' }),
        expect.objectContaining({ conname: 'metering_reservation_tenant_idempotency_key_key', contype: 'u' }),
        expect.objectContaining({ conname: 'metering_reservation_counter_fk', contype: 'f' }),
        expect.objectContaining({
          conname: 'metering_reservation_applied_hard_limit_bounded',
          contype: 'c',
        }),
        expect.objectContaining({ conname: 'metering_usage_event_reservation_id_key', contype: 'u' }),
      ]),
    )

    const checks = constraints.filter((constraint) => constraint.contype === 'c')
    expect(checks.length).toBeGreaterThanOrEqual(5)

    await expect(
      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, status,
          applied_included_units, applied_overage, policy_version, expires_at
        ) VALUES (
          'reservation-1', 'tenant-1', 'key-1', 'fingerprint', 'account-1', 'api', 'period-1',
          NOW(), NOW(), 1, 'reserved', 1, 'deny', 'v1', NOW()
        )
      `),
    ).rejects.toThrow()

    await db.execute(sql`
      INSERT INTO metering_counter (tenant_id, account, meter, period_id, committed, reserved)
      VALUES ('tenant-1', 'account-1', 'api', 'period-1', 0, 0)
    `)
    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, status,
        applied_included_units, applied_overage, policy_version, expires_at
      ) VALUES (
        'reservation-1', 'tenant-1', 'key-1', 'fingerprint', 'account-1', 'api', 'period-1',
        NOW(), NOW(), 1, 'reserved', 1, 'deny', 'v1', NOW()
      )
    `)

    await expect(
      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, status,
          applied_included_units, applied_overage, policy_version, expires_at
        ) VALUES (
          'reservation-2', 'tenant-1', 'key-1', 'fingerprint', 'account-1', 'api', 'period-1',
          NOW(), NOW(), 1, 'reserved', 1, 'deny', 'v1', NOW()
        )
      `),
    ).rejects.toThrow()
  })

  it('wraps DDL failures in a structural migration error', async () => {
    const failure = new Error('database unavailable')
    const db = {
      execute: async () => {
        throw failure
      },
    } as unknown as Parameters<typeof pushSchema>[0]

    let thrown: unknown
    try {
      await pushSchema(db)
    } catch (error) {
      thrown = error
    }

    expect(isMeteringMigrateError(thrown)).toBe(true)
    expect(thrown).toMatchObject({ name: 'MeteringMigrateError', code: 'METERING_MIGRATE' })
    expect((thrown as { detail: string }).detail).toBe('database unavailable')
  })
})
