// Walks the audit chain in seq order, recomputes hashes, and validates each
// checkpoint signature. Returns the position of the first detected break OR
// a clean ok with the count of entries verified.
//
// Three classes of failure surface separately so the operator can act on
// them:
//   - 'event-hash-mismatch' — an event's stored hash doesn't match the
//     recompute. Modification in place.
//   - 'event-prev-hash-mismatch' — an event's prevHash doesn't match the
//     prior event's hash. Insertion/deletion in the middle.
//   - 'checkpoint-signature-mismatch' — a checkpoint's signature doesn't
//     match the recomputed HMAC. Checkpoint tampered OR signing key
//     rotated without a key-rotation event (the latter is not yet
//     implemented; for now treat as tampering).

import { Injectable, Logger } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { AuditService } from './audit.service.js'
import { CheckpointService } from './checkpoint.service.js'
import {
  eventToCanonical,
  GENESIS_HASH,
  hashCanonical,
  verifyCheckpointSignature,
} from './canonical.js'

export type VerifyBreak =
  | { kind: 'event-hash-mismatch'; seq: number; expected: string; actual: string }
  | { kind: 'event-prev-hash-mismatch'; seq: number; expected: string; actual: string }
  | { kind: 'checkpoint-signature-mismatch'; headSeq: number }

export interface VerifyReport {
  ok: boolean
  totalEventsVerified: number
  checkpointsChecked: number
  latestCheckpointAt: string | null
  latestVerifiedSeq: number | null
  break?: VerifyBreak
}

@Injectable()
export class AuditVerifier {
  private readonly logger = new Logger(AuditVerifier.name)
  private readonly key: string

  constructor(
    private readonly audit: AuditService,
    private readonly checkpoints: CheckpointService,
    config: ConfigService,
  ) {
    this.key = config.getOrThrow<string>('AUDIT_SIGNING_KEY')
  }

  async verify(): Promise<VerifyReport> {
    const head = await this.audit.headChainState()
    if (!head) {
      return {
        ok: true,
        totalEventsVerified: 0,
        checkpointsChecked: 0,
        latestCheckpointAt: null,
        latestVerifiedSeq: null,
      }
    }

    // Walk every chained event in seq order, recomputing each hash from its
    // canonical form + the prior event's hash. The first break terminates
    // the walk with a precise location.
    const cursor = this.audit.chainCursor(1, head.headSeq)
    let prevHash = GENESIS_HASH
    let totalVerified = 0
    let lastSeq: number | null = null
    let breakInfo: VerifyBreak | undefined

    for await (const evt of cursor) {
      if (evt.prevHash !== prevHash) {
        breakInfo = {
          kind: 'event-prev-hash-mismatch',
          seq: evt.seq ?? -1,
          expected: prevHash,
          actual: evt.prevHash ?? '<missing>',
        }
        break
      }
      const recomputed = hashCanonical(eventToCanonical(evt))
      if (evt.hash !== recomputed) {
        breakInfo = {
          kind: 'event-hash-mismatch',
          seq: evt.seq ?? -1,
          expected: recomputed,
          actual: evt.hash ?? '<missing>',
        }
        break
      }
      prevHash = evt.hash
      lastSeq = evt.seq ?? lastSeq
      totalVerified++
    }

    if (breakInfo) {
      return {
        ok: false,
        totalEventsVerified: totalVerified,
        checkpointsChecked: 0,
        latestCheckpointAt: null,
        latestVerifiedSeq: lastSeq,
        break: breakInfo,
      }
    }

    // Chain integrity holds end to end. Now validate every checkpoint
    // signature against the same key.
    const checkpoints = await this.checkpoints.list()
    for (const cp of checkpoints) {
      const ok = verifyCheckpointSignature(this.key, cp.headSeq, cp.headHash, cp.at, cp.signature)
      if (!ok) {
        return {
          ok: false,
          totalEventsVerified: totalVerified,
          checkpointsChecked: checkpoints.indexOf(cp),
          latestCheckpointAt: cp.at.toISOString(),
          latestVerifiedSeq: lastSeq,
          break: { kind: 'checkpoint-signature-mismatch', headSeq: cp.headSeq },
        }
      }
    }

    const latest = checkpoints[checkpoints.length - 1]
    return {
      ok: true,
      totalEventsVerified: totalVerified,
      checkpointsChecked: checkpoints.length,
      latestCheckpointAt: latest?.at?.toISOString() ?? null,
      latestVerifiedSeq: lastSeq,
    }
  }
}
