import { $hook, $inject, Alepha } from "alepha";
import { describe, expect, it } from "vitest";
import { $mode } from "../primitives/$mode.ts";

describe("$mode", () => {
  it("should activate when direct env is set", async () => {
    class Target {
      mode = $mode({ env: "MIGRATE" });
    }

    const alepha = Alepha.create({ env: { MIGRATE: "true" } });
    alepha.with(Target);

    const target = alepha.inject(Target);
    expect(target.mode).toBe(true);
  });

  it("should activate when MODE env matches", async () => {
    class Target {
      mode = $mode({ env: "MIGRATE" });
    }

    // Use `new Alepha()` to avoid `process.env` overwriting MODE
    const alepha = new Alepha({ env: { MODE: "MIGRATE" } });
    alepha.with(Target);

    const target = alepha.inject(Target);
    expect(target.mode).toBe(true);
  });

  it("should return false when env does not match", async () => {
    class Target {
      mode = $mode({ env: "MIGRATE" });
    }

    const alepha = Alepha.create({ env: {} });
    alepha.with(Target);

    const target = alepha.inject(Target);
    expect(target.mode).toBe(false);
  });

  it("should prune unrelated services from graph", async () => {
    const stack: string[] = [];

    class Target {
      mode = $mode({ env: "MIGRATE" });

      onStart = $hook({
        on: "start",
        handler: async () => {
          stack.push("Target.start");
        },
      });
    }

    class Unrelated {
      onStart = $hook({
        on: "start",
        handler: async () => {
          stack.push("Unrelated.start");
        },
      });
    }

    const alepha = Alepha.create({ env: { MIGRATE: "true" } });
    alepha.with(Target).with(Unrelated);

    await alepha.start();

    expect(stack).toContain("Target.start");
    expect(stack).not.toContain("Unrelated.start");
  });

  it("should keep dependencies of the mode class", async () => {
    const stack: string[] = [];

    class Dep {
      onStart = $hook({
        on: "start",
        handler: async () => {
          stack.push("Dep.start");
        },
      });
    }

    class Target {
      dep = $inject(Dep);
      mode = $mode({ env: "SEED" });

      onStart = $hook({
        on: "start",
        handler: async () => {
          stack.push("Target.start");
        },
      });
    }

    const alepha = Alepha.create({ env: { SEED: "true" } });
    alepha.with(Target);

    await alepha.start();

    expect(stack).toContain("Dep.start");
    expect(stack).toContain("Target.start");
  });

  it("should fire ready callback then stop", async () => {
    let readyFired = false;

    class Target {
      mode = $mode({
        env: "MIGRATE",
        ready: async () => {
          readyFired = true;
        },
      });
    }

    const alepha = Alepha.create({ env: { MIGRATE: "true" } });
    alepha.with(Target);

    await alepha.start();

    expect(readyFired).toBe(true);
    expect(alepha.isStarted()).toBe(false);
  });

  it("should stop even if ready callback throws", async () => {
    class Target {
      mode = $mode({
        env: "MIGRATE",
        ready: async () => {
          throw new Error("boom");
        },
      });
    }

    const alepha = Alepha.create({ env: { MIGRATE: "true" } });
    alepha.with(Target);

    // The error is wrapped by the event system
    await expect(alepha.start()).rejects.toThrow(/ready/);
    expect(alepha.isStarted()).toBe(false);
  });

  it("should not prune when env does not match", async () => {
    const stack: string[] = [];

    class ModeClass {
      mode = $mode({ env: "MIGRATE" });

      onStart = $hook({
        on: "start",
        handler: async () => {
          stack.push("ModeClass.start");
        },
      });
    }

    class Other {
      onStart = $hook({
        on: "start",
        handler: async () => {
          stack.push("Other.start");
        },
      });
    }

    const alepha = Alepha.create({ env: {} });
    alepha.with(ModeClass).with(Other);

    await alepha.start();

    // Both should fire — no pruning happened
    expect(stack).toContain("ModeClass.start");
    expect(stack).toContain("Other.start");
  });

  it("should preserve substitutions through prune", async () => {
    class Base {
      value = "base";
    }

    class Override extends Base {
      override value = "override";
    }

    class Target {
      dep = $inject(Base);
      mode = $mode({ env: "TEST_MODE" });
    }

    const alepha = Alepha.create({ env: { TEST_MODE: "true" } });
    alepha.with({ provide: Base, use: Override }).with(Target);

    await alepha.start();

    const target = alepha.inject(Target);
    expect(target.dep.value).toBe("override");
  });

  it("should skip .with() calls after target is set (early cutoff)", async () => {
    const stack: string[] = [];

    class Target {
      mode = $mode({ env: "MIGRATE" });

      onStart = $hook({
        on: "start",
        handler: async () => {
          stack.push("Target.start");
        },
      });
    }

    class LateRegistration {
      onStart = $hook({
        on: "start",
        handler: async () => {
          stack.push("LateRegistration.start");
        },
      });
    }

    const alepha = Alepha.create({ env: { MIGRATE: "true" } });
    alepha.with(Target);
    // This .with() should be skipped because target is already set
    alepha.with(LateRegistration);

    await alepha.start();

    expect(stack).toContain("Target.start");
    expect(stack).not.toContain("LateRegistration.start");

    // LateRegistration should not be in the graph at all
    const graph = alepha.graph();
    expect(graph.LateRegistration).toBeUndefined();
  });

  it("should work without ready callback (long-running mode)", async () => {
    const stack: string[] = [];

    class WorkerMode {
      mode = $mode({ env: "WORKER" });

      onReady = $hook({
        on: "ready",
        handler: async () => {
          stack.push("WorkerMode.ready");
        },
      });
    }

    const alepha = Alepha.create({ env: { WORKER: "true" } });
    alepha.with(WorkerMode);

    await alepha.start();

    // Ready hook fires, but no auto-stop
    expect(stack).toContain("WorkerMode.ready");
    expect(alepha.isStarted()).toBe(true);

    await alepha.stop();
  });
});
