// Import the test harness FIRST so the resource catalog is installed
import { mockChatAgent } from "../src/v3/test/index.js";

import type { LanguageModelV3StreamPart } from "@ai-sdk/provider";
import { simulateReadableStream, streamText } from "ai";
import { MockLanguageModelV3 } from "ai/test";
import { describe, expect, it } from "vitest";
import { chat } from "../src/v3/ai.js";

function userMessage(text: string, id?: string) {
  return {
    id: id ?? `u-${Math.random().toString(36).slice(2)}`,
    role: "user" as const,
    parts: [{ type: "text" as const, text }],
  };
}

function textStream(text: string) {
  const chunks: LanguageModelV3StreamPart[] = [
    { type: "text-start", id: "t1" },
    { type: "text-delta", id: "t1", delta: text },
    { type: "text-end", id: "t1" },
    {
      type: "finish",
      finishReason: { unified: "stop", raw: "stop" },
      usage: {
        inputTokens: { total: 10, noCache: 10, cacheRead: undefined, cacheWrite: undefined },
        outputTokens: { total: 10, text: 10, reasoning: undefined },
      },
    },
  ];
  return simulateReadableStream({ chunks });
}

/** Capture the rendered system message handed to the provider. */
type Captured = { system?: { role: string; content: unknown; providerOptions?: any } };

function makeModel(capture: Captured) {
  return new MockLanguageModelV3({
    doStream: async (opts) => {
      capture.system = opts.prompt.find((m) => m.role === "system") as Captured["system"];
      return { stream: textStream("ok") };
    },
  });
}

/** Poll until the mock model captures the system message (bounded), instead of a fixed sleep. */
async function waitForSystemCaptured(capture: Captured, timeoutMs = 1000, intervalMs = 5) {
  const startedAt = Date.now();
  while (!capture.system) {
    if (Date.now() - startedAt > timeoutMs) {
      throw new Error("Timed out waiting for system message capture");
    }
    await new Promise((r) => setTimeout(r, intervalMs));
  }
}

const SYSTEM = "You are a helpful assistant for tests.";

describe("chat prompt caching — system providerOptions", () => {
  it("emits a plain system prompt with no providerOptions by default", async () => {
    const cap: Captured = {};
    const model = makeModel(cap);

    const agent = chat.agent({
      id: "prompt-caching.default",
      onChatStart: async () => {
        chat.prompt.set(SYSTEM);
      },
      run: async ({ messages, signal }) =>
        streamText({ model, messages, abortSignal: signal, ...chat.toStreamTextOptions() }),
    });

    const harness = mockChatAgent(agent, { chatId: "pc-default" });
    try {
      await harness.sendMessage(userMessage("hi"));
      await waitForSystemCaptured(cap);
      expect(cap.system?.content).toContain("helpful assistant");
      expect(cap.system?.providerOptions).toBeUndefined();
    } finally {
      await harness.close();
    }
  });

  it("attaches cacheControl via the toStreamTextOptions sugar", async () => {
    const cap: Captured = {};
    const model = makeModel(cap);

    const agent = chat.agent({
      id: "prompt-caching.sugar",
      onChatStart: async () => {
        chat.prompt.set(SYSTEM);
      },
      run: async ({ messages, signal }) =>
        streamText({
          model,
          messages,
          abortSignal: signal,
          ...chat.toStreamTextOptions({ cacheControl: { type: "ephemeral" } }),
        }),
    });

    const harness = mockChatAgent(agent, { chatId: "pc-sugar" });
    try {
      await harness.sendMessage(userMessage("hi"));
      await waitForSystemCaptured(cap);
      expect(cap.system?.content).toContain("helpful assistant");
      expect(cap.system?.providerOptions?.anthropic?.cacheControl).toEqual({ type: "ephemeral" });
    } finally {
      await harness.close();
    }
  });

  it("attaches systemProviderOptions verbatim", async () => {
    const cap: Captured = {};
    const model = makeModel(cap);

    const agent = chat.agent({
      id: "prompt-caching.explicit",
      onChatStart: async () => {
        chat.prompt.set(SYSTEM);
      },
      run: async ({ messages, signal }) =>
        streamText({
          model,
          messages,
          abortSignal: signal,
          ...chat.toStreamTextOptions({
            systemProviderOptions: {
              anthropic: { cacheControl: { type: "ephemeral", ttl: "1h" } },
            },
          }),
        }),
    });

    const harness = mockChatAgent(agent, { chatId: "pc-explicit" });
    try {
      await harness.sendMessage(userMessage("hi"));
      await waitForSystemCaptured(cap);
      expect(cap.system?.providerOptions?.anthropic?.cacheControl).toEqual({
        type: "ephemeral",
        ttl: "1h",
      });
    } finally {
      await harness.close();
    }
  });

  it("carries providerOptions set on chat.prompt.set()", async () => {
    const cap: Captured = {};
    const model = makeModel(cap);

    const agent = chat.agent({
      id: "prompt-caching.prompt-set",
      onChatStart: async () => {
        chat.prompt.set(SYSTEM, {
          providerOptions: { anthropic: { cacheControl: { type: "ephemeral" } } },
        });
      },
      run: async ({ messages, signal }) =>
        streamText({ model, messages, abortSignal: signal, ...chat.toStreamTextOptions() }),
    });

    const harness = mockChatAgent(agent, { chatId: "pc-prompt-set" });
    try {
      await harness.sendMessage(userMessage("hi"));
      await waitForSystemCaptured(cap);
      expect(cap.system?.providerOptions?.anthropic?.cacheControl).toEqual({ type: "ephemeral" });
    } finally {
      await harness.close();
    }
  });

  it("call-site systemProviderOptions overrides chat.prompt.set providerOptions", async () => {
    const cap: Captured = {};
    const model = makeModel(cap);

    const agent = chat.agent({
      id: "prompt-caching.precedence",
      onChatStart: async () => {
        chat.prompt.set(SYSTEM, {
          providerOptions: { anthropic: { cacheControl: { type: "ephemeral" } } },
        });
      },
      run: async ({ messages, signal }) =>
        streamText({
          model,
          messages,
          abortSignal: signal,
          ...chat.toStreamTextOptions({
            systemProviderOptions: {
              anthropic: { cacheControl: { type: "ephemeral", ttl: "1h" } },
            },
          }),
        }),
    });

    const harness = mockChatAgent(agent, { chatId: "pc-precedence" });
    try {
      await harness.sendMessage(userMessage("hi"));
      await waitForSystemCaptured(cap);
      // The call-site option wins (ttl: "1h"), not the prompt-set default.
      expect(cap.system?.providerOptions?.anthropic?.cacheControl).toEqual({
        type: "ephemeral",
        ttl: "1h",
      });
    } finally {
      await harness.close();
    }
  });
});
