import { describe, expect, it } from "vitest";
import {
  canMoveFundsOnV3,
  evaluateVersionedAction,
  type GateEvidence,
  type VersionedActionRequest,
} from "../src/v3-action-gate";
import { DEFAULT_V3_FLAGS, type V3FeatureFlags } from "../src/version-flags";

const ALL_FLAGS_ON: V3FeatureFlags = {
  v3UiEnabled: true,
  v3ReadsEnabled: true,
  v3WritesEnabled: true,
  v3MoneyMovementEnabled: true,
  v3AuditedPilotEnabled: true,
};

const HEALTHY: GateEvidence = {
  flags: ALL_FLAGS_ON,
  protocolStatus: { status: "ok", dataSourceErrors: [] },
  pilotGate: { pilotReady: true },
};

const v2Withdraw: VersionedActionRequest = {
  action: "vault.withdraw",
  movesFunds: true,
  contractName: "VaultfireBIPVault",
  chain: "base",
};

const v3Withdraw: VersionedActionRequest = {
  action: "vault.withdraw",
  movesFunds: true,
  contractName: "VaultfireBIPVaultV3",
  chain: "base",
  selection: { selectedVersion: "v3", v3Acknowledged: true },
};

describe("V2 actions are untouched", () => {
  it("allows a V2 money-moving action with all V3 flags off and no gate evidence", () => {
    const decision = evaluateVersionedAction(v2Withdraw);
    expect(decision.allowed).toBe(true);
    expect(decision.version).toBe("v2");
    expect(decision.blockers).toEqual([]);
    expect(decision.deployment?.address).toMatch(/^0x[0-9a-fA-F]{40}$/);
    expect(decision.labels).toContain("Legacy V2");
  });

  it("routes an unselected session to V2 even when every V3 flag is on", () => {
    const decision = evaluateVersionedAction(v2Withdraw, HEALTHY);
    expect(decision.version).toBe("v2");
    expect(decision.allowed).toBe(true);
  });
});

describe("V3 money movement fails closed", () => {
  it("blocks by default (flags off, no evidence)", () => {
    const decision = evaluateVersionedAction(v3Withdraw);
    expect(decision.allowed).toBe(false);
    expect(decision.blockers).toEqual(
      expect.arrayContaining([
        "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-pilot-gate-unavailable",
      ]),
    );
    expect(decision.labels).toEqual(["Read-only"]);
  });

  it("never silently downgrades a V3 intent to a V2 money movement", () => {
    const decision = evaluateVersionedAction(v3Withdraw, { flags: DEFAULT_V3_FLAGS });
    expect(decision.version).toBe("v3");
    expect(decision.allowed).toBe(false);
    expect(decision.deployment).toBeNull();
  });

  it("blocks when the user did not explicitly acknowledge V3", () => {
    const decision = evaluateVersionedAction(
      { ...v3Withdraw, selection: { selectedVersion: "v3" } },
      HEALTHY,
    );
    expect(decision.blockers).toContain("v3-not-acknowledged");
    expect(decision.allowed).toBe(false);
  });

  it("blocks when the V3 deployment address is absent", () => {
    // No V3 address exists in the canonical inventory yet, so even a fully
    // enabled, fully healthy environment cannot move funds on V3.
    const decision = evaluateVersionedAction(v3Withdraw, HEALTHY);
    expect(decision.blockers).toEqual(["v3-deployment-address-missing"]);
    expect(decision.allowed).toBe(false);
    expect(canMoveFundsOnV3(v3Withdraw, HEALTHY)).toBe(false);
  });

  it("blocks when protocol status is missing, degraded, or reports data-source errors", () => {
    const base = { ...HEALTHY, pilotGate: { pilotReady: true } };
    expect(
      evaluateVersionedAction(v3Withdraw, { ...base, protocolStatus: null }).blockers,
    ).toContain("v3-protocol-status-unavailable");
    expect(
      evaluateVersionedAction(v3Withdraw, {
        ...base,
        protocolStatus: { status: "degraded", dataSourceErrors: ["live-status-reader-unconfigured"] },
      }).blockers,
    ).toContain("v3-protocol-status-degraded");
    expect(
      evaluateVersionedAction(v3Withdraw, {
        ...base,
        protocolStatus: { status: "ok", dataSourceErrors: ["indexer-lag"] },
      }).blockers,
    ).toContain("v3-protocol-status-degraded");
  });

  it("blocks when the pilot gate is missing or not ready", () => {
    expect(evaluateVersionedAction(v3Withdraw, { ...HEALTHY, pilotGate: null }).blockers).toContain(
      "v3-pilot-gate-unavailable",
    );
    expect(
      evaluateVersionedAction(v3Withdraw, { ...HEALTHY, pilotGate: { pilotReady: false } }).blockers,
    ).toContain("v3-pilot-gate-not-ready");
  });

  it("blocks a money-moving action when only writes are enabled", () => {
    const flags: V3FeatureFlags = {
      ...ALL_FLAGS_ON,
      v3MoneyMovementEnabled: false,
      v3AuditedPilotEnabled: false,
    };
    const decision = evaluateVersionedAction(v3Withdraw, { ...HEALTHY, flags });
    expect(decision.blockers).toEqual(
      expect.arrayContaining(["v3-money-movement-flag-off", "v3-audited-pilot-flag-off"]),
    );
    expect(decision.allowed).toBe(false);
  });
});

describe("V3 read-only actions", () => {
  const v3Read: VersionedActionRequest = {
    action: "bond.view",
    movesFunds: false,
    writes: false,
    contractName: "MandateRegistryV3",
    chain: "base",
    selection: { selectedVersion: "v3", v3Acknowledged: true },
  };

  it("do not require money-movement or pilot evidence, but still need an address", () => {
    const decision = evaluateVersionedAction(v3Read, {
      flags: { ...DEFAULT_V3_FLAGS, v3UiEnabled: true, v3ReadsEnabled: true },
    });
    expect(decision.blockers).toEqual(["v3-deployment-address-missing"]);
    expect(decision.blockers).not.toContain("v3-pilot-gate-unavailable");
  });

  it("are blocked when the V3 UI or read flags are off", () => {
    const decision = evaluateVersionedAction(v3Read);
    expect(decision.blockers).toEqual(
      expect.arrayContaining(["v3-ui-flag-off", "v3-reads-flag-off"]),
    );
  });
});
