/**
 * H33 Authority State Replay — TypeScript reference implementation.
 *
 * NOT the canonical engine. Canonical Rust lives in
 * scif-backend/src/agent_zero/astate_replay.rs.
 *
 * This implementation exists for one purpose: to be different from Rust and
 * different from the browser JavaScript, so that conformance testing actually
 * proves something. If three independent implementations produce byte-identical
 * canonical outputs across the entire corpus, the algorithm specification is
 * confirmed.
 *
 * Mirrors astate_replay.rs's logic. Uses js-sha3 for SHA3-256.
 */

import { sha3_256 } from "js-sha3";

// ──────────────────────────────────────────────────────────────────────────
// Event types — kept verbatim with the canonical Rust enum names lowered.
// ──────────────────────────────────────────────────────────────────────────

export type Grant = {
  kind: "grant";
  at_ms: number;
  authority_id: string;
  granted_by: string;
  granted_to: string;
  scope: string[];
  effective_expiry_at_ms: number;
  policy_basis: string | null;
};

export type Revoke = {
  kind: "revoke";
  at_ms: number;
  authority_id: string;
};

export type Delegate = {
  kind: "delegate";
  at_ms: number;
  authority_id: string;
  delegated_to: string;
};

export type PolicyRegister = {
  kind: "policy_register";
  at_ms: number;
  policy_id: string;
};

export type PolicyDeprecate = {
  kind: "policy_deprecate";
  at_ms: number;
  policy_id: string;
};

export type AgentCreate = {
  kind: "agent_create";
  at_ms: number;
  agent_id: string;
};

export type AgentRetire = {
  kind: "agent_retire";
  at_ms: number;
  agent_id: string;
};

export type Challenge = {
  kind: "challenge";
  at_ms: number;
  challenge_id: string;
  authority_id: string;
};

export type ChallengeResolved = {
  kind: "challenge_resolved";
  at_ms: number;
  challenge_id: string;
  outcome: "revoked" | "upheld";
};

export type AuthEvent =
  | Grant
  | Revoke
  | Delegate
  | PolicyRegister
  | PolicyDeprecate
  | AgentCreate
  | AgentRetire
  | Challenge
  | ChallengeResolved;

// ──────────────────────────────────────────────────────────────────────────
// Output types.
// ──────────────────────────────────────────────────────────────────────────

export type ReplayResult = {
  verdict: ReplayVerdict;
  snapshot: AuthorityStateSnapshot;
  excluded_authorities: ExcludedAuthority[];
  explanations: ReplayExplanation[];
};

export type ReplayVerdict =
  | "valid"
  | "incomplete"
  | "provenance_broken"
  | "policy_missing"
  | "authority_conflict"
  | "revoked_chain";

export type AuthorityStateSnapshot = {
  format: "h33-authority-state-snapshot-v1";
  tenant_id: string;
  timestamp_t_ms: number;
  computed_at_ms: number;
  state_id: string;
  signature_envelope: SignatureEnvelope;
  active_grants: ActiveGrant[];
  active_policies: string[];
  active_revocations: ActiveRevocation[];
  principals_with_authority: PrincipalWithAuthority[];
  delegation_graph_snapshot: DelegationEdge[];
};

export type SignatureEnvelope = {
  signing_engine_id: "q-sign-h33-replay";
  signing_engine_version: "1.0.0";
  signing_engine_public_key_fingerprint: string;
  signing_message: string;
  signing_message_algorithm: "sha3-256";
  pq_signatures: {
    ml_dsa_65: string;
    falcon_512: string;
    slh_dsa_128f: string;
  };
};

export type ActiveGrant = {
  authority_id: string;
  granted_by: string;
  granted_to: string;
  effective_expiry_at_ms: number;
};

export type ActiveRevocation = {
  authority_id: string;
  revoked_at_ms: number;
};

export type PrincipalWithAuthority = {
  principal_id: string;
  authority_id: string;
  effective_scope: string[];
};

export type DelegationEdge = {
  from_principal: string;
  to_principal: string;
  authority_id: string;
};

export type ExcludedAuthority = {
  authority_id: string;
  originally_granted_at_ms: number;
  granted_to: string;
  reason_code:
    | "policy_missing"
    | "provenance_broken"
    | "revoked_ancestor"
    | "expired"
    | "challenged";
  reason: string;
};

export type ReplayExplanation = {
  authority_id: string;
  included: boolean;
  reason: string;
};

// ──────────────────────────────────────────────────────────────────────────
// Canonical JSON encoding (RFC 8785 JCS approximation).
// ──────────────────────────────────────────────────────────────────────────

export function canonicalJson(value: unknown): string {
  if (value === null || typeof value === "boolean" || typeof value === "number") {
    return JSON.stringify(value);
  }
  if (typeof value === "string") {
    return JSON.stringify(value);
  }
  if (Array.isArray(value)) {
    return "[" + value.map(canonicalJson).join(",") + "]";
  }
  if (typeof value === "object") {
    const obj = value as Record<string, unknown>;
    const keys = Object.keys(obj).sort();
    return (
      "{" +
      keys.map((k) => JSON.stringify(k) + ":" + canonicalJson(obj[k])).join(",") +
      "}"
    );
  }
  throw new Error("Unsupported value: " + typeof value);
}

// ──────────────────────────────────────────────────────────────────────────
// Provenance tracing — same algorithm as astate_replay.rs trace_provenance().
// ──────────────────────────────────────────────────────────────────────────

type ProvenanceOutcome =
  | { outcome: "reachable" }
  | { outcome: "chain_broken" }
  | { outcome: "revoked_ancestor" };

function traceProvenance(
  grants: Map<string, Grant>,
  revocations: Map<string, number>,
  startAuthId: string,
  rootPrincipal: string,
  asOf: number
): ProvenanceOutcome {
  let currentAuthId: string | null = startAuthId;
  const seen = new Set<string>();

  while (currentAuthId) {
    if (seen.has(currentAuthId)) return { outcome: "chain_broken" };
    seen.add(currentAuthId);

    const grant = grants.get(currentAuthId);
    if (!grant) return { outcome: "chain_broken" };

    const rev = revocations.get(currentAuthId);
    if (rev !== undefined && rev <= asOf) return { outcome: "revoked_ancestor" };

    if (grant.granted_by === rootPrincipal) return { outcome: "reachable" };

    // Find the grant that gave `grant.granted_by` its authority.
    let parentGrant: string | null = null;
    for (const [authId, g] of grants) {
      if (g.granted_to === grant.granted_by && g.at_ms <= grant.at_ms) {
        parentGrant = authId;
        break;
      }
    }
    if (!parentGrant) return { outcome: "chain_broken" };
    currentAuthId = parentGrant;
  }

  return { outcome: "chain_broken" };
}

// ──────────────────────────────────────────────────────────────────────────
// Replay engine.
// ──────────────────────────────────────────────────────────────────────────

export function replayUntil(
  events: AuthEvent[],
  t: number,
  tenantId: string,
  rootPrincipal: string
): ReplayResult {
  const sorted = events
    .filter((e) => e.at_ms <= t)
    .slice()
    .sort((a, b) => a.at_ms - b.at_ms);

  const grants = new Map<string, Grant>();
  const revocations = new Map<string, number>();
  const policiesRegistered = new Set<string>();
  const policiesDeprecated = new Set<string>();
  const agentsActive = new Set<string>();
  const conflicts: string[] = [];
  const openChallenges = new Map<string, { opened_at_ms: number; authority_id: string }>();
  let incompleteObserved = false;

  for (const ev of sorted) {
    switch (ev.kind) {
      case "grant": {
        if (grants.has(ev.authority_id)) {
          conflicts.push(ev.authority_id);
          continue;
        }
        grants.set(ev.authority_id, ev);
        break;
      }
      case "revoke": {
        if (!grants.has(ev.authority_id)) incompleteObserved = true;
        revocations.set(ev.authority_id, ev.at_ms);
        break;
      }
      case "delegate":
        break;
      case "policy_register":
        policiesRegistered.add(ev.policy_id);
        break;
      case "policy_deprecate":
        policiesDeprecated.add(ev.policy_id);
        break;
      case "agent_create":
        agentsActive.add(ev.agent_id);
        break;
      case "agent_retire":
        agentsActive.delete(ev.agent_id);
        break;
      case "challenge":
        openChallenges.set(ev.challenge_id, {
          opened_at_ms: ev.at_ms,
          authority_id: ev.authority_id,
        });
        break;
      case "challenge_resolved": {
        const open = openChallenges.get(ev.challenge_id);
        if (open) {
          openChallenges.delete(ev.challenge_id);
          if (ev.outcome === "revoked") {
            revocations.set(open.authority_id, ev.at_ms);
          }
        } else {
          incompleteObserved = true;
        }
        break;
      }
    }
  }

  const explanations: ReplayExplanation[] = [];
  const excluded: ExcludedAuthority[] = [];
  let policyMissingObserved = false;
  let provenanceBrokenObserved = false;
  let revokedChainObserved = false;

  const sortedAuthIds = Array.from(grants.keys()).sort();
  for (const authId of sortedAuthIds) {
    const grant = grants.get(authId)!;

    if (grant.policy_basis && !policiesRegistered.has(grant.policy_basis)) {
      excluded.push({
        authority_id: authId,
        originally_granted_at_ms: grant.at_ms,
        granted_to: grant.granted_to,
        reason_code: "policy_missing",
        reason: `Excluded: policy ${grant.policy_basis} was not registered before the grant.`,
      });
      explanations.push({
        authority_id: authId,
        included: false,
        reason: `Policy basis ${grant.policy_basis} not registered before grant.`,
      });
      policyMissingObserved = true;
      continue;
    }

    const revokedAt = revocations.get(authId);
    if (revokedAt !== undefined && revokedAt <= t) {
      explanations.push({
        authority_id: authId,
        included: false,
        reason: `Revoked at ${revokedAt} (before query time ${t}).`,
      });
      continue;
    }

    if (grant.effective_expiry_at_ms <= t) {
      explanations.push({
        authority_id: authId,
        included: false,
        reason: `Expired at ${grant.effective_expiry_at_ms} (before query time ${t}).`,
      });
      continue;
    }

    const prov = traceProvenance(grants, revocations, authId, rootPrincipal, t);
    if (prov.outcome === "reachable") {
      const polClause = grant.policy_basis ? `; policy ${grant.policy_basis}` : "";
      explanations.push({
        authority_id: authId,
        included: true,
        reason: `Granted by ${grant.granted_by} to ${grant.granted_to}${polClause}; chain to root verified.`,
      });
    } else if (prov.outcome === "chain_broken") {
      excluded.push({
        authority_id: authId,
        originally_granted_at_ms: grant.at_ms,
        granted_to: grant.granted_to,
        reason_code: "provenance_broken",
        reason: `Excluded: cannot trace grantor ${grant.granted_by} back to tenant root ${rootPrincipal}.`,
      });
      explanations.push({
        authority_id: authId,
        included: false,
        reason: `Provenance broken — grantor ${grant.granted_by} not reachable from tenant root.`,
      });
      provenanceBrokenObserved = true;
    } else {
      excluded.push({
        authority_id: authId,
        originally_granted_at_ms: grant.at_ms,
        granted_to: grant.granted_to,
        reason_code: "revoked_ancestor",
        reason: `Excluded: an ancestor authority in the delegation chain was revoked before query time.`,
      });
      explanations.push({
        authority_id: authId,
        included: false,
        reason: `Revoked ancestor in delegation chain.`,
      });
      revokedChainObserved = true;
    }
  }

  // Verdict precedence — must match astate_replay.rs:606-618 EXACTLY.
  // Cascading else-if; first match wins. Open challenges DO NOT affect
  // the verdict (Rust src line 622: `let _ = (..., open_challenges);`).
  // Tracked as DIV-001 in agent-zero/verifier/divergence-log.md (closed).
  let verdict: ReplayVerdict;
  if (conflicts.length > 0) verdict = "authority_conflict";
  else if (revokedChainObserved) verdict = "revoked_chain";
  else if (provenanceBrokenObserved) verdict = "provenance_broken";
  else if (policyMissingObserved) verdict = "policy_missing";
  else if (incompleteObserved) verdict = "incomplete";
  else verdict = "valid";
  // Suppress lint warning: openChallenges intentionally unused in verdict
  // computation to match Rust canonical behavior.
  void openChallenges;

  const activeGrants: ActiveGrant[] = [];
  const principalsWithAuthority: PrincipalWithAuthority[] = [];
  for (const authId of sortedAuthIds) {
    const grant = grants.get(authId)!;
    if (excluded.some((e) => e.authority_id === authId)) continue;
    if (revocations.has(authId) && revocations.get(authId)! <= t) continue;
    if (grant.effective_expiry_at_ms <= t) continue;
    activeGrants.push({
      authority_id: authId,
      granted_by: grant.granted_by,
      granted_to: grant.granted_to,
      effective_expiry_at_ms: grant.effective_expiry_at_ms,
    });
    principalsWithAuthority.push({
      principal_id: grant.granted_to,
      authority_id: authId,
      effective_scope: grant.scope.slice().sort(),
    });
  }

  activeGrants.sort((a, b) => a.authority_id.localeCompare(b.authority_id));
  principalsWithAuthority.sort((a, b) => {
    const p = a.principal_id.localeCompare(b.principal_id);
    return p !== 0 ? p : a.authority_id.localeCompare(b.authority_id);
  });
  excluded.sort((a, b) => a.authority_id.localeCompare(b.authority_id));
  explanations.sort((a, b) => a.authority_id.localeCompare(b.authority_id));

  const activeRevocations: ActiveRevocation[] = Array.from(revocations.entries())
    .filter(([, ms]) => ms <= t)
    .map(([authority_id, revoked_at_ms]) => ({ authority_id, revoked_at_ms }))
    .sort((a, b) => a.authority_id.localeCompare(b.authority_id));

  const activePolicies = Array.from(policiesRegistered)
    .filter((p) => !policiesDeprecated.has(p))
    .sort();

  // Build snapshot (state_id deferred until signature_envelope is canonicalized).
  const snapshotPartial = {
    format: "h33-authority-state-snapshot-v1" as const,
    tenant_id: tenantId,
    timestamp_t_ms: t,
    computed_at_ms: t,
    active_grants: activeGrants,
    active_policies: activePolicies,
    active_revocations: activeRevocations,
    principals_with_authority: principalsWithAuthority,
    delegation_graph_snapshot: [] as DelegationEdge[],
  };

  // signing_message = SHA3-256 of canonical-form snapshotPartial (without state_id, without signature_envelope).
  const signingMessage = sha3_256(canonicalJson(snapshotPartial));

  const signature_envelope: SignatureEnvelope = {
    signing_engine_id: "q-sign-h33-replay",
    signing_engine_version: "1.0.0",
    signing_engine_public_key_fingerprint: "0".repeat(64),
    signing_message: signingMessage,
    signing_message_algorithm: "sha3-256",
    pq_signatures: {
      ml_dsa_65: "REPLAY_UNSIGNED",
      falcon_512: "REPLAY_UNSIGNED",
      slh_dsa_128f: "REPLAY_UNSIGNED",
    },
  };

  const snapshotForId = { ...snapshotPartial, signature_envelope };
  const stateId = sha3_256(canonicalJson(snapshotForId));

  const snapshot: AuthorityStateSnapshot = {
    ...snapshotPartial,
    signature_envelope,
    state_id: stateId,
  };

  return {
    verdict,
    snapshot,
    excluded_authorities: excluded,
    explanations,
  };
}
