import { createHmac } from 'node:crypto'
import { describe, expect, it } from 'vitest'

import { RequestAdmission, type IdempotencyReservation, type IdempotencyStore } from '../src/admission.js'
import { AuditTrail, InMemoryAuditStore, type AuditIntegritySigner } from '../src/audit.js'
import { GatewayError, type CallerIdentity } from '../src/contracts.js'
import type { GatewayAuthenticator } from '../src/auth.js'
import { createReadOnlyGateway, READ_ONLY_OPERATIONS, type OpaqueResponse, type ReadOnlyRoute } from '../src/routes.js'
import { createGatewayServer } from '../src/server.js'

const now = new Date('2026-08-14T12:00:00.000Z')
const identity: CallerIdentity = { subject: 'caller-1', email: 'caller@example.test', edge_session_id: 'edge-1', application_credential_id: 'credential-1' }

class Store implements IdempotencyStore<OpaqueResponse> {
  private readonly values = new Map<string, { hash: string; outcome?: OpaqueResponse }>()
  async reserve(reservation: IdempotencyReservation) {
    const key = `${reservation.key.subject}:${reservation.idempotencyKey}`
    const existing = this.values.get(key)
    if (!existing) { this.values.set(key, { hash: reservation.requestHash }); return { kind: 'reserved' as const } }
    if (existing.hash !== reservation.requestHash || !existing.outcome) return { kind: 'conflict' as const }
    return { kind: 'replay' as const, outcome: existing.outcome }
  }
  async complete(reservation: IdempotencyReservation, outcome: OpaqueResponse) { const value = this.values.get(`${reservation.key.subject}:${reservation.idempotencyKey}`); if (!value) throw new Error('missing'); value.outcome = outcome }
  async abandon(reservation: IdempotencyReservation) { this.values.delete(`${reservation.key.subject}:${reservation.idempotencyKey}`) }
}

const signer: AuditIntegritySigner = {
  sign(payload) { return createHmac('sha256', 'test-key').update(payload).digest('hex') },
  verify(payload, signature) { return this.sign(payload) === signature },
}

function request(operation: string): Uint8Array {
  return new TextEncoder().encode(JSON.stringify({
    request_id: `request-${operation}`, idempotency_key: `key-${operation}`, issued_at: now.toISOString(), nonce: `nonce-${operation}`, operation, payload: {},
  }))
}

function server() {
  const receipts = new InMemoryAuditStore()
  const audit = new AuditTrail(receipts, signer, { now: () => now })
  const admission = new RequestAdmission<OpaqueResponse>({
    nonceStore: { async consume() { return true } }, idempotencyStore: new Store(), rateLimiter: { acquire: async () => true }, concurrencyLimiter: { acquire: async () => async () => {} }, now: () => now,
  })
  const routes = READ_ONLY_OPERATIONS.map((operation) => ({
    operation,
    policy: { maxRequestBytes: 2048, maxAgeMilliseconds: 60_000, rate: { maxRequests: 10, windowMilliseconds: 60_000 }, maxConcurrent: 1 },
    parsePayload: (value: unknown) => value,
    parseResponse: (value: unknown) => value as OpaqueResponse,
    adapter: { async dispatch() { return { id: `safe-${operation}` } } },
  })) as readonly ReadOnlyRoute<unknown>[]
  const authenticator: GatewayAuthenticator = { async authenticate() { return identity } }
  return { server: createGatewayServer(authenticator, createReadOnlyGateway(routes, admission, audit), audit), audit }
}

describe('gateway server integration', () => {
  it('authenticates then runs all R0 operations through raw dispatch', async () => {
    const instance = server()
    for (const operation of READ_ONLY_OPERATIONS) {
      await expect(instance.server.dispatch({ authentication: { accessAssertion: 'edge', applicationCredential: 'app' }, rawBody: request(operation) })).resolves.toEqual({ id: `safe-${operation}` })
    }
  })

  it('does not expose deferred runtime operations', async () => {
    const instance = server()
    await expect(instance.server.dispatch({ authentication: { accessAssertion: 'edge', applicationCredential: 'app' }, rawBody: request('projectRead') })).rejects.toEqual(new GatewayError('operation_not_found'))
  })
})
