/**
 * Layer 1: Hook Merger Tests for before_agent_start
 *
 * Validates that modelOverride and providerOverride fields are correctly
 * propagated through the hook merger, including priority ordering and
 * backward compatibility.
 */
import { beforeEach, describe, expect, it } from "vitest";
import { createHookRunner } from "./hooks.js";
import { addStaticTestHooks, addTestHook, TEST_PLUGIN_AGENT_CTX } from "./hooks.test-helpers.js";
import { createEmptyPluginRegistry, type PluginRegistry } from "./registry.js";
import type { PluginHookBeforeAgentStartResult, PluginHookRegistration } from "./types.js";

function addBeforeAgentStartHook(
  registry: PluginRegistry,
  pluginId: string,
  handler: () => PluginHookBeforeAgentStartResult | Promise<PluginHookBeforeAgentStartResult>,
  priority?: number,
) {
  addTestHook({
    registry,
    pluginId,
    hookName: "before_agent_start",
    handler: handler as PluginHookRegistration["handler"],
    priority,
  });
}

const stubCtx = TEST_PLUGIN_AGENT_CTX;

describe("before_agent_start hook merger", () => {
  let registry: PluginRegistry;

  beforeEach(() => {
    registry = createEmptyPluginRegistry();
  });

  const runWithSingleHook = async (result: PluginHookBeforeAgentStartResult, priority?: number) => {
    addBeforeAgentStartHook(registry, "plugin-a", () => result, priority);
    const runner = createHookRunner(registry);
    return await runner.runBeforeAgentStart({ prompt: "hello" }, stubCtx);
  };

  const expectSingleModelOverride = async (modelOverride: string) => {
    const result = await runWithSingleHook({ modelOverride });
    expect(result?.modelOverride).toBe(modelOverride);
    return result;
  };

  const expectMergedBeforeAgentStart = async (
    hooks: Array<{
      pluginId: string;
      result: PluginHookBeforeAgentStartResult;
      priority?: number;
    }>,
    expected: Partial<PluginHookBeforeAgentStartResult>,
  ) => {
    const result = await runWithHooks(hooks);
    expect(result).toEqual(expect.objectContaining(expected));
    return result;
  };

  const runWithHooks = async (
    hooks: Array<{
      pluginId: string;
      result: PluginHookBeforeAgentStartResult;
      priority?: number;
    }>,
  ) => {
    addStaticTestHooks(registry, {
      hookName: "before_agent_start",
      hooks,
    });
    const runner = createHookRunner(registry);
    return await runner.runBeforeAgentStart({ prompt: "hello" }, stubCtx);
  };

  it.each([
    [
      "returns modelOverride from a single plugin",
      { modelOverride: "llama3.3:8b" },
      {
        modelOverride: "llama3.3:8b",
      },
    ],
    [
      "returns providerOverride from a single plugin",
      { providerOverride: "ollama" },
      {
        providerOverride: "ollama",
      },
    ],
    [
      "returns both modelOverride and providerOverride together",
      {
        modelOverride: "llama3.3:8b",
        providerOverride: "ollama",
      },
      {
        modelOverride: "llama3.3:8b",
        providerOverride: "ollama",
      },
    ],
    [
      "systemPrompt merges correctly alongside model overrides",
      {
        systemPrompt: "You are a helpful assistant",
        modelOverride: "llama3.3:8b",
        providerOverride: "ollama",
      },
      {
        systemPrompt: "You are a helpful assistant",
        modelOverride: "llama3.3:8b",
        providerOverride: "ollama",
      },
    ],
  ] as const)("%s", async (_name, hookResult, expected) => {
    await expectMergedBeforeAgentStart([{ pluginId: "plugin-a", result: hookResult }], expected);
  });

  it("higher-priority plugin wins for modelOverride", async () => {
    const result = await expectMergedBeforeAgentStart(
      [
        { pluginId: "low-priority", result: { modelOverride: "gpt-5.4" }, priority: 1 },
        { pluginId: "high-priority", result: { modelOverride: "llama3.3:8b" }, priority: 10 },
      ],
      { modelOverride: "llama3.3:8b" },
    );
    expect(result?.modelOverride).toBe("llama3.3:8b");
  });

  it("lower-priority plugin does not overwrite if it returns undefined", async () => {
    const result = await runWithHooks([
      {
        pluginId: "high-priority",
        result: { modelOverride: "llama3.3:8b", providerOverride: "ollama" },
        priority: 10,
      },
      {
        pluginId: "low-priority",
        result: { prependContext: "some context" },
        priority: 1,
      },
    ]);

    // High-priority ran first (priority 10), low-priority ran second (priority 1).
    // Low-priority didn't return modelOverride, so ?? falls back to acc's value.
    expect(result?.modelOverride).toBe("llama3.3:8b");
    expect(result?.providerOverride).toBe("ollama");
    expect(result?.prependContext).toBe("some context");
  });

  it("prependContext still concatenates when modelOverride is present", async () => {
    const result = await runWithHooks([
      {
        pluginId: "plugin-a",
        result: { prependContext: "context A", modelOverride: "llama3.3:8b" },
        priority: 10,
      },
      {
        pluginId: "plugin-b",
        result: { prependContext: "context B" },
        priority: 1,
      },
    ]);

    expect(result?.prependContext).toBe("context A\n\ncontext B");
    expect(result?.modelOverride).toBe("llama3.3:8b");
  });

  it("backward compat: plugin returning only prependContext produces no modelOverride", async () => {
    const result = await runWithSingleHook({ prependContext: "legacy context" });

    expect(result?.prependContext).toBe("legacy context");
    expect(result?.modelOverride).toBeUndefined();
    expect(result?.providerOverride).toBeUndefined();
  });

  it("modelOverride without providerOverride leaves provider undefined", async () => {
    const result = await expectSingleModelOverride("llama3.3:8b");
    expect(result?.providerOverride).toBeUndefined();
  });

  it("returns undefined when no hooks are registered", async () => {
    const runner = createHookRunner(registry);
    const result = await runner.runBeforeAgentStart({ prompt: "hello" }, stubCtx);

    expect(result).toBeUndefined();
  });

  it("passes runId through the agent context to hook handlers", async () => {
    const registry = createEmptyPluginRegistry();
    let capturedCtx: typeof stubCtx | undefined;
    addTestHook({
      registry,
      pluginId: "ctx-spy",
      hookName: "before_agent_start",
      handler: ((_event: unknown, ctx: typeof stubCtx) => {
        capturedCtx = ctx;
        return {};
      }) as PluginHookRegistration["handler"],
    });

    const runner = createHookRunner(registry);
    await runner.runBeforeAgentStart({ prompt: "test" }, stubCtx);

    expect(capturedCtx).toBeDefined();
    expect(capturedCtx?.runId).toBe("test-run-id");
  });
});
