import { describe, expect, it, vi } from "vitest";
import { unsafeOpaqueId, verifyModelGatewayCapability } from "@awp/contracts";
import { ModelGatewayPlanningProvider } from "../../apps/control-plane/src/planning-model-gateway.js";

const secret = "p".repeat(64);

function request() {
  return {
    sessionId: unsafeOpaqueId("planning:session-test"),
    turnId: unsafeOpaqueId("planning-turn:test"),
    accountId: "account:planner",
    model: "gpt-5.6-luna",
    reasoningEffort: "high",
    prompt: "Plan this project",
  };
}

describe("ModelGatewayPlanningProvider", () => {
  it("uses the Codex-compatible streaming Responses shape and reconstructs final text/provenance", async () => {
    const fetchImpl = vi.fn(async (_url: string | URL | Request, init?: RequestInit) => {
      const headers = new Headers(init?.headers);
      const token = headers.get("authorization")?.replace(/^Bearer /u, "") ?? "";
      expect(verifyModelGatewayCapability(secret, token)).toMatchObject({
        purpose: "planning",
        planningSessionId: "planning:session-test",
        planningTurnId: "planning-turn:test",
        accountId: "account:planner",
      });
      expect(headers.get("accept")).toBe("text/event-stream");
      expect(JSON.parse(String(init?.body))).toEqual({
        model: "gpt-5.6-luna",
        reasoning: { effort: "high" },
        input: [
          {
            role: "user",
            content: [{ type: "input_text", text: "Plan this project" }],
          },
        ],
        store: false,
        stream: true,
      });
      return new Response(
        [
          'event: response.created\ndata: {"type":"response.created","response":{"id":"resp_live"}}',
          'event: response.output_text.delta\ndata: {"type":"response.output_text.delta","delta":"{\\"reply\\":\\"o"}',
          'event: response.output_text.delta\ndata: {"type":"response.output_text.delta","delta":"k\\",\\"disposition\\":{\\"kind\\":\\"none\\"}}"}',
          'event: response.output_text.done\ndata: {"type":"response.output_text.done","text":"{\\"reply\\":\\"ok\\",\\"disposition\\":{\\"kind\\":\\"none\\"}}"}',
          'event: response.completed\ndata: {"type":"response.completed","response":{"id":"resp_live","status":"completed","reasoning":{"effort":"high"}}}',
          "",
        ].join("\n\n"),
        { status: 200, headers: { "content-type": "text/event-stream" } },
      );
    });
    const provider = new ModelGatewayPlanningProvider(
      "http://127.0.0.1:32180/v1",
      secret,
      fetchImpl as typeof fetch,
    );
    expect(provider.supportsProvider("codex")).toBe(true);
    expect(provider.supportsProvider("claude")).toBe(false);

    await expect(provider.invoke(request())).resolves.toEqual({
      providerId: "provider:model-gateway",
      outputText: '{"reply":"ok","disposition":{"kind":"none"}}',
      invocationId: "resp_live",
      observedReasoningEffort: "high",
    });
    expect(fetchImpl).toHaveBeenCalledTimes(1);
  });

  it("fails retryably when a successful stream contains no output text", async () => {
    const provider = new ModelGatewayPlanningProvider(
      "http://127.0.0.1:32180/v1",
      secret,
      (async () =>
        new Response(
          'event: response.completed\ndata: {"type":"response.completed","response":{"id":"resp_empty","status":"completed"}}\n\n',
          { status: 200, headers: { "content-type": "text/event-stream" } },
        )) as typeof fetch,
    );

    await expect(provider.invoke(request())).rejects.toMatchObject({
      category: "adapter-protocol",
      retryable: true,
      safeMessage: "Planner model returned no output text",
    });
  });

  it("loads the real model catalog shape with supported reasoning efforts and hides non-selectable models", async () => {
    const fetchImpl = vi.fn(async (_url: string | URL | Request, init?: RequestInit) => {
      const headers = new Headers(init?.headers);
      expect(headers.get("accept")).toBe("application/json");
      return new Response(
        JSON.stringify({
          models: [
            {
              slug: "gpt-5.6-sol",
              display_name: "GPT-5.6-Sol",
              description: "Frontier model",
              default_reasoning_level: "low",
              supported_reasoning_levels: [
                { effort: "low", description: "Fast" },
                { effort: "high", description: "Deep" },
              ],
              context_window: 272000,
              supported_in_api: true,
              visibility: "list",
            },
            {
              slug: "hidden-review",
              display_name: "Hidden",
              supported_reasoning_levels: [{ effort: "medium" }],
              supported_in_api: true,
              visibility: "hide",
            },
          ],
        }),
        { status: 200, headers: { "content-type": "application/json" } },
      );
    });
    const provider = new ModelGatewayPlanningProvider(
      "http://127.0.0.1:32180/v1",
      secret,
      fetchImpl as typeof fetch,
    );

    await expect(provider.listModels("account:planner")).resolves.toEqual([
      {
        model: "gpt-5.6-sol",
        displayName: "GPT-5.6-Sol",
        description: "Frontier model",
        defaultReasoningEffort: "low",
        reasoningEfforts: [
          { effort: "low", description: "Fast" },
          { effort: "high", description: "Deep" },
        ],
        contextWindow: 272000,
      },
    ]);
    expect(String(fetchImpl.mock.calls[0]?.[0])).toContain("/models?client_version=0.149.1");
  });

  it("fails closed when the provider reports a different reasoning effort than requested", async () => {
    const provider = new ModelGatewayPlanningProvider(
      "http://127.0.0.1:32180/v1",
      secret,
      (async () =>
        new Response(
          [
            'event: response.output_text.done\ndata: {"type":"response.output_text.done","text":"{\\"reply\\":\\"ok\\",\\"disposition\\":{\\"kind\\":\\"none\\"}}"}',
            'event: response.completed\ndata: {"type":"response.completed","response":{"id":"resp_wrong_effort","status":"completed","reasoning":{"effort":"medium"}}}',
            "",
          ].join("\n\n"),
          { status: 200, headers: { "content-type": "text/event-stream" } },
        )) as typeof fetch,
    );

    await expect(provider.invoke(request())).rejects.toMatchObject({
      category: "adapter-protocol",
      retryable: false,
      safeMessage: "Planner provider applied reasoning effort medium instead of high",
    });
  });
});
