import { Effect } from "effect";
import { describe, expect, it } from "vitest";

import {
  SmokeContractError,
  advanceSmokeLoop,
  decodeSmokeChatText,
  decodeSmokeModelCatalog,
  decodeSmokePlan,
  decodeSmokeReview,
  decodeSmokeWorkerReport,
  initialSmokeLoopState,
  redactSmokeText,
  selectSmokeModelRoles,
  validateSmokeCapabilities,
} from "./smoke";

const validRoleSelection = () => ({
  plannerReviewerModel: "glm-5.2",
  plannerReviewerProvider: "cheaptricks",
  workerModel: "minimax-m3",
  workerProvider: "cheaptricks",
  workerToolCallMode: "serial" as const,
});

const run = <A, E>(effect: Effect.Effect<A, E>): A => Effect.runSync(effect);

describe("smoke primitives", () => {
  it("selects the planner/reviewer and serial M3 worker roles", () => {
    const roles = run(selectSmokeModelRoles(validRoleSelection()));

    expect(roles.worker).toMatchObject({
      model: "minimax-m3",
      role: "worker",
      toolCallMode: "serial",
    });
    expect(roles.plannerReviewer).toMatchObject({
      model: "glm-5.2",
      role: "planner-reviewer",
    });
  });

  it("rejects a non-M3 worker or non-GLM planner", () => {
    const worker = validRoleSelection();
    worker.workerModel = "glm-5.2";
    const workerError = run(Effect.flip(selectSmokeModelRoles(worker)));
    expect(workerError).toBeInstanceOf(SmokeContractError);
    expect(workerError.reason).toBe("InvalidModelRole");

    const planner = validRoleSelection();
    planner.plannerReviewerModel = "minimax-m3";
    const plannerError = run(Effect.flip(selectSmokeModelRoles(planner)));
    expect(plannerError.reason).toBe("InvalidModelRole");
  });

  it("advances only through the serial smoke loop", () => {
    let state = initialSmokeLoopState();
    state = run(advanceSmokeLoop(state, "preflight-passed"));
    state = run(advanceSmokeLoop(state, "plan-validated"));
    state = run(advanceSmokeLoop(state, "worker-succeeded"));
    state = run(advanceSmokeLoop(state, "review-approved"));

    expect(state.phase).toBe("completed");
    expect(state.history).toEqual([
      "preflight",
      "planning",
      "worker-running",
      "reviewing",
      "completed",
    ]);
  });

  it("turns contract failures into a terminal state", () => {
    const state = run(
      advanceSmokeLoop(
        initialSmokeLoopState(),
        "contract-failed",
        "daemon is offline"
      )
    );

    expect(state.phase).toBe("failed");
    expect(state.lastError).toBe("daemon is offline");
  });

  it("rejects out-of-order loop transitions", () => {
    const error = run(
      Effect.flip(advanceSmokeLoop(initialSmokeLoopState(), "review-approved"))
    );

    expect(error).toBeInstanceOf(SmokeContractError);
    expect(error.reason).toBe("InvalidTransition");
  });

  it("validates model reports at the primitive boundary", () => {
    const plan = run(
      decodeSmokePlan({
        acceptanceCriteria: ["The button renders"],
        nonGoals: [],
        steps: ["Add the button", "Run the web check"],
        summary: "Add a small button to the disposable project",
      })
    );
    const worker = run(
      decodeSmokeWorkerReport({
        status: "completed",
        summary: "Changed the project and ran the targeted check.",
      })
    );
    const review = run(
      decodeSmokeReview({
        approved: true,
        findings: [],
        summary: "The change matches the acceptance criteria.",
      })
    );

    expect(plan.steps).toHaveLength(2);
    expect(worker.status).toBe("completed");
    expect(review.approved).toBe(true);
  });

  it("rejects empty or malformed model output", () => {
    const empty = run(
      Effect.flip(
        decodeSmokeChatText({ choices: [{ message: { content: "   " } }] })
      )
    );
    expect(empty.reason).toBe("InvalidModelResponse");

    const malformed = run(Effect.flip(decodeSmokeReview({ approved: true })));
    expect(malformed.reason).toBe("InvalidReport");
  });

  it("validates the canonical CPA model catalog", () => {
    const catalog = run(
      decodeSmokeModelCatalog({
        data: [{ id: "minimax-m3" }, { id: "glm-5.2" }],
      })
    );

    expect(catalog.data.map(({ id }) => id)).toEqual(["minimax-m3", "glm-5.2"]);
  });

  it("fails a capability report when any required check fails", () => {
    const error = run(
      Effect.flip(
        validateSmokeCapabilities([
          { id: "web", message: "ready", passed: true },
          { id: "daemon", message: "daemon is offline", passed: false },
        ])
      )
    );

    expect(error.reason).toBe("InvalidReport");
    expect(error.message).toContain("daemon is offline");
  });

  it("redacts secrets without changing unrelated text", () => {
    expect(
      redactSmokeText("status token=secret-key endpoint=ok", ["secret-key"])
    ).toBe("status token=[REDACTED] endpoint=ok");
  });
});
