/* eslint-disable no-non-null-assertion -- test assertions on defined objects */
import { Effect } from "effect";
import { describe, expect, it } from "vitest";

import { OrbConfigurationError } from "./domain";
import { makeOrbEvent } from "./events";
import type { OrbEvent } from "./events";
import { OrbHandle, OrbRuntime } from "./runtime";

const validGateway = {
  apiKey: "test-key",
  baseUrl: "https://gw.example.com/v1",
  model: "m1",
  provider: "p1",
};

const validContext = {
  artifacts: [],
  contextFiles: [],
  issueBody: "Do the thing",
  issueTitle: "Thing",
  repositoryUrl: undefined,
};

const validIdentity = {
  projectId: "prj-1",
  runId: "run-1",
  workUnitId: "wrk-1",
};

describe("OrbRuntime.createOrb validation", () => {
  it("rejects missing gateway API key before touching Docker", () => {
    const runtime = new OrbRuntime();
    // This must fail at validation, not at Docker — if Docker is the failure,
    // that indicates the validation ordering is wrong.
    const error = Effect.runSync(
      Effect.flip(
        runtime.createOrb({
          context: validContext,
          docker: {
            containerName: "orb-test",
            hostWorkspacePath: "/tmp/orb-test",
          },
          gateway: { ...validGateway, apiKey: "  " },
          identity: validIdentity,
        })
      )
    );
    expect(error).toBeInstanceOf(OrbConfigurationError);
    expect(error.reason).toBe("MissingGateway");
  });
});

describe("OrbHandle event lifecycle", () => {
  it("emits events to listeners", () => {
    const handle = new OrbHandle(
      "orb-x" as never,
      "run-x" as never,
      validIdentity
    );
    const events: OrbEvent[] = [];
    handle.onEvent((e) => events.push(e));

    handle.emitEvent(makeOrbEvent(1, "vm_booted", { text: "booted" }));
    handle.emitEvent(
      makeOrbEvent(2, "command_executed", { command: "ls", exitCode: 0 })
    );

    expect(events.length).toBe(2);
    expect(events[0]!.type).toBe("vm_booted");
    expect(events[1]!.type).toBe("command_executed");
    expect(events[1]!.command).toBe("ls");
  });

  it("unsubscribes listeners correctly", () => {
    const handle = new OrbHandle(
      "orb-y" as never,
      "run-y" as never,
      validIdentity
    );
    const events: OrbEvent[] = [];
    const unsub = handle.onEvent((e) => events.push(e));

    handle.emitEvent(makeOrbEvent(1, "session_opened"));
    unsub();
    handle.emitEvent(makeOrbEvent(2, "session_closed"));

    expect(events.length).toBe(1);
  });
});

describe("OrbHandle state transitions", () => {
  it("starts in creating state", () => {
    const handle = new OrbHandle(
      "orb-z" as never,
      "run-z" as never,
      validIdentity
    );
    expect(handle.state).toBe("creating");
  });

  it("transitions to prepared then running", () => {
    const handle = new OrbHandle(
      "orb-a" as never,
      "run-a" as never,
      validIdentity
    );
    Effect.runSync(handle.setOrbState("prepared"));
    expect(handle.state).toBe("prepared");
    Effect.runSync(handle.setOrbState("running"));
    expect(handle.state).toBe("running");
  });

  it("rejects invalid transition", () => {
    const handle = new OrbHandle(
      "orb-b" as never,
      "run-b" as never,
      validIdentity
    );
    const error = Effect.runSync(
      Effect.flip(handle.setOrbState("needs-input"))
    );
    expect(error.reason).toBe("InvalidTransition");
  });
});
