/**
 * Fail-closed gate for versioned website actions.
 *
 * Rules encoded here:
 *  - V2 actions are untouched: no V3 flag, gate, or label can block or reroute
 *    an existing V2 action.
 *  - A V3 action requires explicit user selection of V3 in this session.
 *  - A V3 money-moving action additionally requires the money-movement flag,
 *    a verified V3 deployment address, a healthy protocol status report, and a
 *    ready pilot gate. Missing inputs (null/undefined) are blockers, never
 *    assumed-good defaults.
 *  - A V3 intent is never silently downgraded to a V2 money movement.
 */

import {
  DEFAULT_V3_FLAGS,
  resolveActiveVersion,
  type ProtocolVersion,
  type V3FeatureFlags,
  type VersionSelection,
} from "./version-flags";
import { resolveTruthLabels, type TruthLabel } from "./truth-labels";
import {
  findDeployment,
  type WebsiteAddressEntry,
  type WebsiteChain,
} from "../generated/website-addresses.generated";

export type GateBlocker =
  | "v3-not-explicitly-selected"
  | "v3-not-acknowledged"
  | "v3-ui-flag-off"
  | "v3-reads-flag-off"
  | "v3-writes-flag-off"
  | "v3-money-movement-flag-off"
  | "v3-audited-pilot-flag-off"
  | "v3-deployment-address-missing"
  | "v3-protocol-status-unavailable"
  | "v3-protocol-status-degraded"
  | "v3-pilot-gate-unavailable"
  | "v3-pilot-gate-not-ready";

export type VersionedActionRequest = {
  /** Stable action id, e.g. "bond.create" or "vault.withdraw". */
  readonly action: string;
  /** True for anything that transfers value or creates a payout obligation. */
  readonly movesFunds: boolean;
  /** True for any state change, including non-financial writes. */
  readonly writes?: boolean;
  readonly contractName: string;
  readonly chain: WebsiteChain;
  readonly selection?: VersionSelection;
};

/** Live gate evidence. `null`/`undefined` means "unknown", which blocks V3. */
export type GateEvidence = {
  readonly flags?: V3FeatureFlags;
  readonly protocolStatus?: {
    readonly status: string;
    readonly dataSourceErrors?: readonly string[];
  } | null;
  readonly pilotGate?: { readonly pilotReady: boolean } | null;
};

export type GateDecision = {
  readonly allowed: boolean;
  /** Version the action would execute against. Never "v3" when blocked. */
  readonly version: ProtocolVersion;
  readonly labels: readonly TruthLabel[];
  readonly blockers: readonly GateBlocker[];
  readonly deployment: WebsiteAddressEntry | null;
};

function decision(value: GateDecision): GateDecision {
  return Object.freeze({ ...value, blockers: Object.freeze([...value.blockers]) });
}

export function evaluateVersionedAction(
  request: VersionedActionRequest,
  evidence: GateEvidence = {},
): GateDecision {
  const flags = evidence.flags ?? DEFAULT_V3_FLAGS;
  const selection = request.selection ?? {};
  const writes = request.writes ?? request.movesFunds;

  // --- V2 path: unchanged behavior, no V3 dependency. ---
  if (selection.selectedVersion !== "v3") {
    const deployment = findDeployment(request.contractName, request.chain, "v2");
    return decision({
      allowed: true,
      version: "v2",
      labels: resolveTruthLabels(
        { protocolVersion: "v2", deployment, writesAvailable: writes },
        flags,
      ),
      blockers: [],
      deployment,
    });
  }

  // --- V3 path: explicit selection present; every gate must be proven. ---
  const blockers: GateBlocker[] = [];
  if (resolveActiveVersion(selection, flags) !== "v3") blockers.push("v3-ui-flag-off");
  if (!flags.v3ReadsEnabled) blockers.push("v3-reads-flag-off");
  if (!selection.v3Acknowledged) blockers.push("v3-not-acknowledged");

  const deployment = findDeployment(request.contractName, request.chain, "v3");
  if (!deployment) blockers.push("v3-deployment-address-missing");

  if (writes && !flags.v3WritesEnabled) blockers.push("v3-writes-flag-off");

  if (request.movesFunds) {
    if (!flags.v3MoneyMovementEnabled) blockers.push("v3-money-movement-flag-off");
    if (!flags.v3AuditedPilotEnabled) blockers.push("v3-audited-pilot-flag-off");

    const status = evidence.protocolStatus;
    if (status === undefined || status === null) {
      blockers.push("v3-protocol-status-unavailable");
    } else if (status.status !== "ok" || (status.dataSourceErrors?.length ?? 0) > 0) {
      blockers.push("v3-protocol-status-degraded");
    }

    const pilot = evidence.pilotGate;
    if (pilot === undefined || pilot === null) {
      blockers.push("v3-pilot-gate-unavailable");
    } else if (pilot.pilotReady !== true) {
      blockers.push("v3-pilot-gate-not-ready");
    }
  }

  const allowed = blockers.length === 0;
  return decision({
    allowed,
    // A blocked V3 intent stays on V3 so the UI reports the failure instead of
    // quietly executing the same action against V2.
    version: "v3",
    labels: resolveTruthLabels(
      { protocolVersion: "v3", deployment, writesAvailable: allowed && writes },
      flags,
    ),
    blockers,
    deployment,
  });
}

/** Convenience wrapper for money-moving surfaces. */
export function canMoveFundsOnV3(
  request: Omit<VersionedActionRequest, "movesFunds">,
  evidence: GateEvidence = {},
): boolean {
  return evaluateVersionedAction({ ...request, movesFunds: true }, evidence).allowed;
}
