// Commit backend — commit.op, commit-tested.op, commit/rewrite.op
import type { EffectBackend } from "../../../src/core/effects/backend";
import type { State } from "../../../src/core/eval/machine";
import type { OpCall } from "../../../src/core/effects/opcall";
import type { Val } from "../../../src/core/eval/values";
import type { CommitAdapter } from "../../../src/core/effects/runtimeImpl";
import type { Profile } from "../../../src/package-bridge/governance";
import { capRequire, capHas, type CapSet } from "../../../src/package-bridge/governance";
import { isMeaning, type MeaningVal, type Evidence } from "../../oracle/core/src/meaning";
import { matchAST } from "../../oracle/core/src/match";
import type { SnapshotRepo } from "../../oracle/core/src/snapshots";
import type { ReceiptStore } from "../../oracle/core/src/receipts";
import { PortalImpl } from "../../oracle/core/src/portalImpl";
import { compileTextToExpr } from "../../../src/core/pipeline/compileText";
import { runToCompletionWithState } from "../../../src/core/eval/run";
import type { Runtime } from "../../../src/core/eval/runtime";

function requireCommitCapability(caps: CapSet, required: string, context: string): void {
  if (capHas(caps, "commit.*")) return;
  capRequire(caps, required, context);
}

function obligationSatisfied(m: MeaningVal): boolean {
  const o = m?.obligation;
  if (!o) return false;
  if ((o as any).tag === "Bool") return !!(o as any).b;
  if ((o as any).tag === "Obligation" && (o as any).status === "satisfied") return true;
  return false;
}

export class CommitBackend implements EffectBackend {
  readonly ops = ["commit.op", "commit-tested.op", "commit/rewrite.op"] as const;

  constructor(
    private readonly commit: CommitAdapter,
    private readonly profile: Profile,
    private readonly runtime: Runtime,
    private readonly snapshots: SnapshotRepo,
    private readonly receipts: ReceiptStore,
  ) {}

  async handle(state: State, opcall: OpCall): Promise<State> {
    if (opcall.op === "commit.op") return this.handleCommit(state, opcall);
    if (opcall.op === "commit-tested.op") return this.handleCommitTested(state, opcall);
    if (opcall.op === "commit/rewrite.op") return this.handleCommitRewrite(state, opcall);
    throw new Error(`CommitBackend: unhandled op ${opcall.op}`);
  }

  private async handleCommit(_state: State, opcall: OpCall): Promise<State> {
    requireCommitCapability(this.profile.caps, "commit.method", "commit");

    const kind = opcall.args[0]?.tag === "Str" ? (opcall.args[0] as any).s : "unknown";
    const payload = opcall.args[1] ?? opcall.args[0] ?? { tag: "Unit" };

    if (this.profile.truth === "speculative") {
      throw new Error(`commit rejected in speculative truth regime (kind=${kind})`);
    }

    if (this.profile.truth === "test-certified" || this.profile.truth === "proof-certified") {
      if (isMeaning(payload)) {
        if (!obligationSatisfied(payload)) {
          throw new Error("commit requires satisfied obligations under non-speculative regimes");
        }
      }
    }

    const res = await this.commit.commit(payload, opcall.ctxDigest);
    return opcall.resumption.invoke(res) as State;
  }

  private extractTestThunks(v: Val): Val[] {
    const result: Val[] = [];
    let cur = v;
    while ((cur as any).tag === "Vector" && (cur as any).items?.length === 2) {
      result.push((cur as any).items[0]);
      cur = (cur as any).items[1];
    }
    if ((cur as any).tag === "Vector" && (cur as any).items?.length > 0 && result.length === 0) {
      return (cur as any).items;
    }
    return result;
  }

  private async handleCommitTested(state: State, opcall: OpCall): Promise<State> {
    requireCommitCapability(this.profile.caps, "commit.method", "commit-tested");
    capRequire(this.profile.caps, "test", "commit-tested");

    let kind = "tested";
    let payload: Val;
    let tests: Val[];

    if (opcall.args.length >= 3 && opcall.args[0]?.tag === "Str") {
      kind = (opcall.args[0] as any).s;
      payload = opcall.args[1];
      tests = this.extractTestThunks(opcall.args[2]);
    } else if (opcall.args.length >= 2) {
      payload = opcall.args[0];
      tests = this.extractTestThunks(opcall.args[1]);
    } else {
      throw new Error("commit-tested.op: expected (kind payload tests) or (payload tests)");
    }

    const envRef = this.snapshots.putEnv({ env: state.env, store: state.store });
    const portal = new PortalImpl(this.runtime as any, this.snapshots, this.receipts, {
      maxEvalSteps: 100_000,
      parseText: (src: string) => compileTextToExpr(src),
      caps: this.profile.caps,
    });

    for (let i = 0; i < tests.length; i++) {
      const testThunk = tests[i];
      if (testThunk.tag !== "Closure") {
        throw new Error(`commit-tested.op: test ${i} is not a closure`);
      }

      const { env, store } = this.snapshots.getEnv(envRef);
      const testState: State = {
        control: { tag: "Expr", e: testThunk.body },
        env: testThunk.env,
        store,
        kont: [],
        handlers: [],
      };

      try {
        const { value } = await runToCompletionWithState(this.runtime as any, testState, 100_000);
        const passed = value.tag === "Bool" ? value.b : (value.tag !== "Unit");
        if (!passed) {
          throw new Error(`commit-tested.op: test ${i} failed (returned ${value.tag})`);
        }
      } catch (e: any) {
        throw new Error(`commit-tested.op: test ${i} threw: ${e?.message ?? e}`);
      }
    }

    if (this.profile.truth === "speculative") {
      throw new Error(`commit rejected in speculative truth regime (kind=${kind})`);
    }

    const result = await this.commit.commit(payload!, opcall.ctxDigest);
    return opcall.resumption.invoke(result) as State;
  }

  private async handleCommitRewrite(state: State, opcall: OpCall): Promise<State> {
    requireCommitCapability(this.profile.caps, "commit.rewrite", "commit/rewrite");

    const meaningArg = opcall.args[0];
    if (!meaningArg || !isMeaning(meaningArg)) {
      throw new Error("commit/rewrite.op: expected Meaning value");
    }

    const meaning = meaningArg as MeaningVal;
    const obligations = meaning.obligations ?? [];
    const evidence: Evidence[] = [];

    const envRef = this.snapshots.putEnv({ env: state.env, store: state.store });
    const portal = new PortalImpl(this.runtime as any, this.snapshots, this.receipts, {
      maxEvalSteps: 100_000,
      parseText: (src: string) => compileTextToExpr(src),
      caps: this.profile.caps,
    });

    for (const obl of obligations) {
      if (obl.tag === "OblTests") {
        let passed = 0;
        const total = obl.tests.length;

        for (const testExpr of obl.tests) {
          const { env, store } = this.snapshots.getEnv(obl.envRef ?? envRef);
          const testState: State = {
            control: { tag: "Expr", e: testExpr },
            env,
            store,
            kont: [],
            handlers: [],
          };

          try {
            const { value } = await runToCompletionWithState(this.runtime as any, testState, 100_000);
            const ok = value.tag === "Bool" ? value.b : (value.tag !== "Unit");
            if (ok) passed++;
            else throw new Error(`OblTests: test failed`);
          } catch (e: any) {
            throw new Error(`commit/rewrite.op: OblTests failed: ${e?.message ?? e}`);
          }
        }

        evidence.push({ tag: "TestEvidence", passed, total });
      }

      if (obl.tag === "OblNoMatch") {
        const targets: Val[] = [];
        const unwrap = (v: Val): unknown => {
          if ((v as any)?.tag === "Syntax" && "stx" in (v as any)) return (v as any).stx;
          return v;
        };
        if (obl.scope === "all") {
          if (meaning.rewrite) targets.push(meaning.rewrite);
          if (meaning.residual) targets.push(meaning.residual);
        } else if (meaning.rewrite) {
          targets.push(meaning.rewrite);
        }

        for (const target of targets) {
          const { ok } = matchAST(obl.pattern, unwrap(target));
          if (ok) {
            throw new Error(`commit/rewrite.op: OblNoMatch failed - pattern found in ${obl.scope}`);
          }
        }

        evidence.push({
          tag: "NoMatchEvidence",
          pattern: obl.pattern,
          searched: targets.length,
          found: 0,
        });
      }

      if (obl.tag === "OblEqExt") {
        let allPassed = true;
        const failures: Array<{ input: Val; expected: Val; got: Val }> = [];

        for (const testExpr of obl.tests) {
          const { env, store } = this.snapshots.getEnv(obl.envRef ?? envRef);

          const origState: State = {
            control: { tag: "Expr", e: { tag: "App", fn: obl.original, args: [testExpr] } },
            env,
            store,
            kont: [],
            handlers: [],
          };
          const { value: expected } = await runToCompletionWithState(this.runtime as any, origState, 100_000);

          const candState: State = {
            control: { tag: "Expr", e: { tag: "App", fn: obl.candidate, args: [testExpr] } },
            env,
            store,
            kont: [],
            handlers: [],
          };
          const { value: got } = await runToCompletionWithState(this.runtime as any, candState, 100_000);

          const eq = JSON.stringify(expected) === JSON.stringify(got);
          if (!eq) {
            allPassed = false;
            failures.push({ input: { tag: "Syntax", stx: testExpr } as any, expected, got });
          }
        }

        if (!allPassed) {
          throw new Error(`commit/rewrite.op: OblEqExt failed - ${failures.length} test(s) differ`);
        }
        evidence.push({ tag: "EqExtEvidence", tests: obl.tests.length, allPassed, failures: failures.length > 0 ? failures : undefined });
      }
    }

    if (this.profile.truth === "speculative") {
      throw new Error(`commit/rewrite rejected in speculative truth regime`);
    }

    const resultMeaning: MeaningVal = {
      ...meaning,
      evidence,
    };

    const payload = meaning.rewrite ?? meaning.residual ?? meaning.denotation ?? { tag: "Unit" };
    const result = await this.commit.commit(payload as Val, opcall.ctxDigest);

    return opcall.resumption.invoke(resultMeaning as Val) as State;
  }
}
