import { describe, expect, it } from "bun:test";
import { convertAnthropicMessages } from "@oh-my-pi/pi-ai/providers/anthropic";
import { transformMessages } from "@oh-my-pi/pi-ai/providers/transform-messages";
import type { AssistantMessage, Model, UserMessage } from "@oh-my-pi/pi-ai/types";

/**
 * Regression: some Anthropic-routed models reject "assistant prefill" requests
 * (messages ending with an assistant turn). We should automatically append a
 * synthetic user message to keep the request valid.
 */
describe("Anthropic assistant-prefill fallback", () => {
	const model: Model<"anthropic-messages"> = {
		api: "anthropic-messages",
		provider: "anthropic",
		id: "claude-3-5-sonnet-20241022",
		name: "Claude 3.5 Sonnet",
		baseUrl: "https://api.anthropic.com",
		input: ["text"],
		cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
		maxTokens: 8192,
		contextWindow: 200000,
		reasoning: true,
	};

	it("appends a user Continue. message when the last turn is assistant", () => {
		const user: UserMessage = {
			role: "user",
			content: "Output JSON",
			timestamp: Date.now(),
		};
		const assistantPrefill: AssistantMessage = {
			role: "assistant",
			content: [{ type: "text", text: "{" }],
			api: "anthropic-messages",
			provider: "anthropic",
			model: model.id,
			usage: {
				input: 0,
				output: 0,
				cacheRead: 0,
				cacheWrite: 0,
				totalTokens: 0,
				cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
			},
			stopReason: "stop",
			timestamp: Date.now(),
		};

		const params = convertAnthropicMessages([user, assistantPrefill], model, false);
		expect(params.at(-1)?.role).toBe("user");
		expect(params.at(-1)?.content).toBe("Continue.");
	});

	it("does not append Continue. when the last turn is already user", () => {
		const params = convertAnthropicMessages(
			[
				{ role: "user", content: "hi", timestamp: Date.now() },
				{
					role: "assistant",
					content: [{ type: "text", text: "hello" }],
					api: "anthropic-messages",
					provider: "anthropic",
					model: model.id,
					usage: {
						input: 0,
						output: 0,
						cacheRead: 0,
						cacheWrite: 0,
						totalTokens: 0,
						cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
					},
					stopReason: "stop",
					timestamp: Date.now(),
				},
				{ role: "user", content: "what now?", timestamp: Date.now() },
			],
			model,
			false,
		);
		expect(params.at(-1)?.role).toBe("user");
		expect(params.at(-1)?.content).toBe("what now?");
	});
});

it("preserves redacted thinking blocks in assistant replay payloads", () => {
	const model: Model<"anthropic-messages"> = {
		api: "anthropic-messages",
		provider: "anthropic",
		id: "claude-3-5-sonnet-20241022",
		name: "Claude 3.5 Sonnet",
		baseUrl: "https://api.anthropic.com",
		input: ["text"],
		cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
		maxTokens: 8192,
		contextWindow: 200000,
		reasoning: true,
	};
	const user: UserMessage = {
		role: "user",
		content: "continue",
		timestamp: Date.now(),
	};
	const assistant: AssistantMessage = {
		role: "assistant",
		content: [
			{ type: "thinking", thinking: "internal", thinkingSignature: "sig_1" },
			{ type: "redactedThinking", data: "encrypted_payload" },
			{ type: "text", text: "Final answer" },
		],
		api: "anthropic-messages",
		provider: "anthropic",
		model: model.id,
		usage: {
			input: 0,
			output: 0,
			cacheRead: 0,
			cacheWrite: 0,
			totalTokens: 0,
			cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
		},
		stopReason: "stop",
		timestamp: Date.now(),
	};

	const params = convertAnthropicMessages([user, assistant], model, false);
	const assistantParam = params.find(m => m.role === "assistant");
	expect(assistantParam).toBeDefined();
	expect(Array.isArray(assistantParam?.content)).toBe(true);
	const blocks = assistantParam?.content as unknown as Array<Record<string, unknown>>;
	expect(blocks.map(block => block.type)).toEqual(["thinking", "redacted_thinking", "text"]);
	expect(blocks[0]?.signature).toBe("sig_1");
	expect(blocks[1]?.data).toBe("encrypted_payload");
});

it("preserves latest Anthropic thinking blocks even when model id changes", () => {
	const model: Model<"anthropic-messages"> = {
		api: "anthropic-messages",
		provider: "anthropic",
		id: "claude-3-5-sonnet-20241022",
		name: "Claude 3.5 Sonnet",
		baseUrl: "https://api.anthropic.com",
		input: ["text"],
		cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
		maxTokens: 8192,
		contextWindow: 200000,
		reasoning: true,
	};
	const switchedModel: Model<"anthropic-messages"> = { ...model, id: "claude-opus-4-6-20251201" };
	const assistant: AssistantMessage = {
		role: "assistant",
		content: [
			{ type: "thinking", thinking: "internal", thinkingSignature: "sig_2" },
			{ type: "redactedThinking", data: "encrypted_payload_2" },
			{ type: "text", text: "Answer" },
		],
		api: "anthropic-messages",
		provider: "anthropic",
		model: "claude-sonnet-4-6-20251201",
		usage: {
			input: 0,
			output: 0,
			cacheRead: 0,
			cacheWrite: 0,
			totalTokens: 0,
			cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
		},
		stopReason: "stop",
		timestamp: Date.now(),
	};

	const transformed = transformMessages(
		[{ role: "user", content: "continue", timestamp: Date.now() }, assistant],
		switchedModel,
	);
	const transformedAssistant = transformed.find(m => m.role === "assistant") as AssistantMessage | undefined;
	expect(transformedAssistant).toBeDefined();
	expect(transformedAssistant?.content[0]).toEqual(assistant.content[0]);
	expect(transformedAssistant?.content[1]).toEqual(assistant.content[1]);
});

it("strips invalid thinking signatures from aborted Anthropic replay messages", () => {
	const model: Model<"anthropic-messages"> = {
		api: "anthropic-messages",
		provider: "anthropic",
		id: "claude-3-5-sonnet-20241022",
		name: "Claude 3.5 Sonnet",
		baseUrl: "https://api.anthropic.com",
		input: ["text"],
		cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
		maxTokens: 8192,
		contextWindow: 200000,
		reasoning: true,
	};
	const assistant: AssistantMessage = {
		role: "assistant",
		content: [
			{ type: "thinking", thinking: "partial reasoning", thinkingSignature: "sig_partial" },
			{ type: "text", text: "partial answer" },
		],
		api: "anthropic-messages",
		provider: "anthropic",
		model: model.id,
		usage: {
			input: 0,
			output: 0,
			cacheRead: 0,
			cacheWrite: 0,
			totalTokens: 0,
			cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
		},
		stopReason: "aborted",
		timestamp: Date.now(),
	};

	const transformed = transformMessages(
		[{ role: "user", content: "continue", timestamp: Date.now() }, assistant],
		model,
	);
	const transformedAssistant = transformed.find(m => m.role === "assistant") as AssistantMessage | undefined;

	expect(transformedAssistant).toBeDefined();
	const thinkingBlock = transformedAssistant?.content[0];
	expect(thinkingBlock).toMatchObject({ type: "thinking", thinking: "partial reasoning" });
	expect(
		thinkingBlock && "thinkingSignature" in thinkingBlock ? thinkingBlock.thinkingSignature : undefined,
	).toBeUndefined();
});