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

import {
  orbActorKey,
  OrbStateError,
  transitionOrbState,
  transitionRunState,
} from "./domain";

const runTransition = (from: string, to: string) =>
  Effect.runSync(
    Effect.flip(transitionOrbState({ from: from as never, to: to as never }))
  );

const runTransitionOk = (from: string, to: string) =>
  Effect.runSync(transitionOrbState({ from: from as never, to: to as never }));

const runRunState = (from: string, to: string) =>
  Effect.runSync(
    Effect.flip(transitionRunState({ from: from as never, to: to as never }))
  );

const runRunStateOk = (from: string, to: string) =>
  Effect.runSync(transitionRunState({ from: from as never, to: to as never }));

describe("orbActorKey", () => {
  it("produces a stable key from the identity triple", () => {
    const key = orbActorKey({
      projectId: "prj-1",
      runId: "run-1",
      workUnitId: "wrk-1",
    });
    expect(key).toBe("project:prj-1:work:wrk-1:run:run-1");
  });

  it("preserves order across different identities", () => {
    expect(orbActorKey({ projectId: "a", runId: "b", workUnitId: "c" })).toBe(
      "project:a:work:c:run:b"
    );
  });
});

describe("transitionOrbState", () => {
  it("allows creating -> prepared", () => {
    expect(runTransitionOk("creating", "prepared")).toBe("prepared");
  });

  it("allows prepared -> running", () => {
    expect(runTransitionOk("prepared", "running")).toBe("running");
  });

  it("allows running -> needs-input", () => {
    expect(runTransitionOk("running", "needs-input")).toBe("needs-input");
  });

  it("allows running -> completed", () => {
    expect(runTransitionOk("running", "completed")).toBe("completed");
  });

  it("allows completed -> disposed", () => {
    expect(runTransitionOk("completed", "disposed")).toBe("disposed");
  });

  it("allows needs-input -> running (resume)", () => {
    expect(runTransitionOk("needs-input", "running")).toBe("running");
  });

  it("rejects disposed -> running", () => {
    const error = runTransition("disposed", "running");
    expect(error).toBeInstanceOf(OrbStateError);
    expect(error.reason).toBe("AlreadyDisposed");
  });

  it("rejects completed -> creating", () => {
    const error = runTransition("completed", "creating");
    expect(error).toBeInstanceOf(OrbStateError);
    expect(error.reason).toBe("InvalidTransition");
  });

  it("rejects prepared -> needs-input (skipping running)", () => {
    const error = runTransition("prepared", "needs-input");
    expect(error).toBeInstanceOf(OrbStateError);
    expect(error.reason).toBe("InvalidTransition");
  });

  it("rejects cancelled -> running", () => {
    const error = runTransition("cancelled", "running");
    expect(error).toBeInstanceOf(OrbStateError);
    expect(error.reason).toBe("InvalidTransition");
  });
});

describe("transitionRunState", () => {
  it("allows queued -> provisioning", () => {
    expect(runRunStateOk("queued", "provisioning")).toBe("provisioning");
  });

  it("allows provisioning -> preparing", () => {
    expect(runRunStateOk("provisioning", "preparing")).toBe("preparing");
  });

  it("allows running -> verifying", () => {
    expect(runRunStateOk("running", "verifying")).toBe("verifying");
  });

  it("allows verifying -> succeeded", () => {
    expect(runRunStateOk("verifying", "succeeded")).toBe("succeeded");
  });

  it("allows running -> cancelled", () => {
    expect(runRunStateOk("running", "cancelled")).toBe("cancelled");
  });

  it("rejects succeeded -> running (terminal)", () => {
    const error = runRunState("succeeded", "running");
    expect(error).toBeInstanceOf(OrbStateError);
    expect(error.reason).toBe("InvalidTransition");
  });

  it("rejects failed -> running (terminal)", () => {
    const error = runRunState("failed", "running");
    expect(error).toBeInstanceOf(OrbStateError);
    expect(error.reason).toBe("InvalidTransition");
  });

  it("rejects queued -> succeeded (skipping steps)", () => {
    const error = runRunState("queued", "succeeded");
    expect(error).toBeInstanceOf(OrbStateError);
    expect(error.reason).toBe("InvalidTransition");
  });
});
