import { describe, test, expect } from 'vitest'
import { createMemoryStores } from '../src/store/memory.js'
import { createHostRegistry } from '../src/registry/hosts.js'
import { createMeteringCollector, MeteringError } from '../src/metering/collect.js'
import { fingerprint } from '../src/ca/fingerprint.js'
import { generateEd25519 } from '../src/util/crypto.js'
import type { NodeIdentity } from '../src/node-auth/identity.js'

const node: NodeIdentity = { nodeId: 'spiffe://relay/node-a' }
const ACCOUNT = '11111111-1111-4111-8111-111111111111'

async function withHost() {
  const stores = createMemoryStores()
  const hosts = createHostRegistry({ hosts: stores.hosts })
  const { publicKeyRaw } = generateEd25519()
  const host = await hosts.bindHost({ accountId: ACCOUNT, subdomain: 'alice', agentPubkey: publicKeyRaw, enrollFpr: fingerprint(publicKeyRaw) })
  const collector = createMeteringCollector({ metering: stores.metering, hosts })
  return { stores, hosts, host, collector }
}

describe('T12 metering (INV1/INV3-analog/INV8)', () => {
  test('ingestSample derives accountId + nodeId server-side; append-only', async () => {
    const { stores, host, collector } = await withHost()
    await collector.ingestSample(node, { hostId: host.hostId, concurrentViewers: 3, sampledAt: new Date().toISOString() })
    const rows = await stores.metering.query(ACCOUNT, '1970-01-01T00:00:00.000Z', '2999-01-01T00:00:00.000Z')
    expect(rows.length).toBe(1)
    expect(rows[0]?.accountId).toBe(ACCOUNT)
    expect(rows[0]?.nodeId).toBe(node.nodeId)
  })

  test('forged attribution: unknown hostId rejected (cross-tenant metering impossible, INV1)', async () => {
    const { collector } = await withHost()
    await expect(
      collector.ingestSample(node, { hostId: '99999999-9999-4999-8999-999999999999', concurrentViewers: 1, sampledAt: new Date().toISOString() }),
    ).rejects.toBeInstanceOf(MeteringError)
  })

  test('pairedHostCount excludes revoked hosts', async () => {
    const { hosts, host, collector } = await withHost()
    expect(await collector.pairedHostCount(ACCOUNT)).toBe(1)
    await hosts.setHostStatus(host.hostId, 'revoked')
    expect(await collector.pairedHostCount(ACCOUNT)).toBe(0)
  })

  test('rollupUsage computes viewer peak + viewer-hours over a window', async () => {
    const { host, collector } = await withHost()
    const t0 = '2026-06-30T00:00:00.000Z'
    const t1 = '2026-06-30T01:00:00.000Z'
    const to = '2026-06-30T02:00:00.000Z'
    await collector.ingestSample(node, { hostId: host.hostId, concurrentViewers: 2, sampledAt: t0 })
    await collector.ingestSample(node, { hostId: host.hostId, concurrentViewers: 4, sampledAt: t1 })
    const usage = await collector.rollupUsage(ACCOUNT, t0, to)
    expect(usage.viewerPeak).toBe(4)
    expect(usage.viewerHours).toBeCloseTo(2 * 1 + 4 * 1, 5) // 2 viewers×1h + 4 viewers×1h
    expect(usage.pairedHostPeak).toBe(1)
  })

  test('malformed sample → Zod error', async () => {
    const { collector } = await withHost()
    await expect(collector.ingestSample(node, { hostId: 'not-a-uuid', concurrentViewers: -1, sampledAt: 'x' })).rejects.toBeInstanceOf(MeteringError)
  })
})
