/**
 * dev-engine-wrapper.test.ts — Contract tests for the dev engine wrapper layer (S02).
 *
 * Tests bridgeDispatchAction mapping, DevWorkflowEngine delegation,
 * DevExecutionPolicy stubs, resolver routing, kill switch, and
 * auto.ts engine ID accessors.
 */

import test, { describe, before, after } from "node:test";
import assert from "node:assert/strict";
import { mkdtempSync, mkdirSync, rmSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";

// ── bridgeDispatchAction mapping ────────────────────────────────────────────

describe("bridgeDispatchAction", () => {
  test("maps dispatch action with step fields", async () => {
    const { bridgeDispatchAction } = await import(
      "../dev-workflow-engine.ts"
    );
    const result = bridgeDispatchAction({
      action: "dispatch",
      unitType: "execute-task",
      unitId: "T01",
      prompt: "do stuff",
      matchedRule: "foo",
    } as any);

    assert.equal(result.action, "dispatch");
    assert.ok("step" in result);
    const step = (result as any).step;
    assert.equal(step.unitType, "execute-task");
    assert.equal(step.unitId, "T01");
    assert.equal(step.prompt, "do stuff");
  });

  test("maps stop action with reason and level", async () => {
    const { bridgeDispatchAction } = await import(
      "../dev-workflow-engine.ts"
    );
    const result = bridgeDispatchAction({
      action: "stop",
      reason: "done",
      level: "info",
      matchedRule: "bar",
    } as any);

    assert.equal(result.action, "stop");
    assert.equal((result as any).reason, "done");
    assert.equal((result as any).level, "info");
  });

  test("maps skip action", async () => {
    const { bridgeDispatchAction } = await import(
      "../dev-workflow-engine.ts"
    );
    const result = bridgeDispatchAction({
      action: "skip",
      matchedRule: "baz",
    } as any);

    assert.equal(result.action, "skip");
  });
});

// ── DevWorkflowEngine ───────────────────────────────────────────────────────

describe("DevWorkflowEngine", () => {
  test("engineId is 'dev'", async () => {
    const { DevWorkflowEngine } = await import("../dev-workflow-engine.ts");
    const engine = new DevWorkflowEngine();
    assert.equal(engine.engineId, "dev");
  });

  test("deriveState returns EngineState with expected fields", async (t) => {
    const { DevWorkflowEngine } = await import("../dev-workflow-engine.ts");
    const engine = new DevWorkflowEngine();

    // Create a minimal temp .sf structure for deriveState
    const tempDir = mkdtempSync(join(tmpdir(), "sf-engine-test-"));
    mkdirSync(join(tempDir, ".sf", "milestones"), { recursive: true });

    t.after(() => rmSync(tempDir, { recursive: true, force: true }));

    const state = await engine.deriveState(tempDir);

    assert.equal(typeof state.phase, "string", "phase should be a string");
    assert.ok(
      "currentMilestoneId" in state,
      "state should have currentMilestoneId",
    );
    assert.ok(
      "activeSliceId" in state,
      "state should have activeSliceId",
    );
    assert.ok(
      "activeTaskId" in state,
      "state should have activeTaskId",
    );
    assert.equal(
      typeof state.isComplete,
      "boolean",
      "isComplete should be boolean",
    );
    assert.ok("raw" in state, "state should have raw field");
  });

  test("reconcile returns continue for non-complete state", async () => {
    const { DevWorkflowEngine } = await import("../dev-workflow-engine.ts");
    const engine = new DevWorkflowEngine();

    const state = {
      phase: "executing",
      currentMilestoneId: "M001",
      activeSliceId: "S01",
      activeTaskId: "T01",
      isComplete: false,
      raw: {},
    };

    const result = await engine.reconcile(state, {
      unitType: "execute-task",
      unitId: "T01",
      startedAt: Date.now() - 1000,
      finishedAt: Date.now(),
    });

    assert.equal(result.outcome, "continue");
  });

  test("reconcile returns milestone-complete for complete state", async () => {
    const { DevWorkflowEngine } = await import("../dev-workflow-engine.ts");
    const engine = new DevWorkflowEngine();

    const state = {
      phase: "complete",
      currentMilestoneId: "M001",
      activeSliceId: null,
      activeTaskId: null,
      isComplete: true,
      raw: {},
    };

    const result = await engine.reconcile(state, {
      unitType: "execute-task",
      unitId: "T01",
      startedAt: Date.now() - 1000,
      finishedAt: Date.now(),
    });

    assert.equal(result.outcome, "milestone-complete");
  });

  test("getDisplayMetadata returns expected fields", async () => {
    const { DevWorkflowEngine } = await import("../dev-workflow-engine.ts");
    const engine = new DevWorkflowEngine();

    const state = {
      phase: "executing",
      currentMilestoneId: "M001",
      activeSliceId: "S01",
      activeTaskId: "T01",
      isComplete: false,
      raw: {},
    };

    const meta = engine.getDisplayMetadata(state);

    assert.ok("engineLabel" in meta, "should have engineLabel");
    assert.ok("currentPhase" in meta, "should have currentPhase");
    assert.ok("progressSummary" in meta, "should have progressSummary");
    assert.ok("stepCount" in meta, "should have stepCount");
    assert.equal(meta.engineLabel, "SF Dev");
  });
});

// ── DevExecutionPolicy stubs ────────────────────────────────────────────────

describe("DevExecutionPolicy", () => {
  test("verify returns 'continue'", async () => {
    const { DevExecutionPolicy } = await import(
      "../dev-execution-policy.ts"
    );
    const policy = new DevExecutionPolicy();
    const result = await policy.verify("execute-task", "T01", {
      basePath: "/tmp",
    });
    assert.equal(result, "continue");
  });

  test("selectModel returns null", async () => {
    const { DevExecutionPolicy } = await import(
      "../dev-execution-policy.ts"
    );
    const policy = new DevExecutionPolicy();
    const result = await policy.selectModel("execute-task", "T01", {
      basePath: "/tmp",
    });
    assert.equal(result, null);
  });

  test("recover returns { outcome: 'retry' }", async () => {
    const { DevExecutionPolicy } = await import(
      "../dev-execution-policy.ts"
    );
    const policy = new DevExecutionPolicy();
    const result = await policy.recover("execute-task", "T01", {
      basePath: "/tmp",
    });
    assert.deepEqual(result, { outcome: "retry" });
  });

  test("closeout returns { committed: false, artifacts: [] }", async () => {
    const { DevExecutionPolicy } = await import(
      "../dev-execution-policy.ts"
    );
    const policy = new DevExecutionPolicy();
    const result = await policy.closeout("execute-task", "T01", {
      basePath: "/tmp",
      startedAt: Date.now(),
    });
    assert.deepEqual(result, { committed: false, artifacts: [] });
  });

  test("prepareWorkspace resolves without error", async () => {
    const { DevExecutionPolicy } = await import(
      "../dev-execution-policy.ts"
    );
    const policy = new DevExecutionPolicy();
    await assert.doesNotReject(
      () => policy.prepareWorkspace("/tmp", "M001"),
      "prepareWorkspace should resolve without error",
    );
  });
});

// ── Resolver routing ────────────────────────────────────────────────────────

describe("Resolver routing", () => {
  test("resolveEngine returns dev engine for null activeEngineId", async () => {
    const { resolveEngine } = await import("../engine-resolver.ts");
    const result = resolveEngine({ activeEngineId: null });
    assert.ok(result.engine, "should return engine");
    assert.ok(result.policy, "should return policy");
    assert.equal(result.engine.engineId, "dev");
  });

  test("resolveEngine returns dev engine for 'dev' activeEngineId", async () => {
    const { resolveEngine } = await import("../engine-resolver.ts");
    const result = resolveEngine({ activeEngineId: "dev" });
    assert.ok(result.engine, "should return engine");
    assert.ok(result.policy, "should return policy");
    assert.equal(result.engine.engineId, "dev");
  });

  test("resolveEngine throws for unknown activeEngineId without activeRunDir", async () => {
    const { resolveEngine } = await import("../engine-resolver.ts");
    assert.throws(
      () => resolveEngine({ activeEngineId: "unknown" }),
      /requires activeRunDir/,
      "should throw when activeRunDir is missing for non-dev engine",
    );
  });
});

// ── Kill switch ─────────────────────────────────────────────────────────────

describe("Kill switch (SF_ENGINE_BYPASS)", () => {
  const originalBypass = process.env.SF_ENGINE_BYPASS;

  after(() => {
    // Restore original env var state
    if (originalBypass === undefined) {
      delete process.env.SF_ENGINE_BYPASS;
    } else {
      process.env.SF_ENGINE_BYPASS = originalBypass;
    }
  });

  test("SF_ENGINE_BYPASS=1 does not affect resolveEngine (bypass checked in autoLoop)", async (t) => {
    const { resolveEngine } = await import("../engine-resolver.ts");
    process.env.SF_ENGINE_BYPASS = "1";
    t.after(() => delete process.env.SF_ENGINE_BYPASS);

    // resolveEngine should still resolve normally — bypass is checked in autoLoop
    const { engine } = resolveEngine({ activeEngineId: null });
    assert.ok(engine, "should return an engine even with bypass set");
  });
});

// ── auto.ts engine ID accessors ─────────────────────────────────────────────

describe("auto.ts engine ID accessors", () => {
  test("setActiveEngineId / getActiveEngineId round-trip", async () => {
    const { setActiveEngineId, getActiveEngineId } = await import(
      "../auto.ts"
    );

    setActiveEngineId("dev");
    assert.equal(
      getActiveEngineId(),
      "dev",
      "getActiveEngineId should return 'dev' after setting",
    );

    setActiveEngineId(null);
    assert.equal(
      getActiveEngineId(),
      null,
      "getActiveEngineId should return null after setting null",
    );
  });
});
