import { describe, expect, it } from "bun:test";
import { Effort } from "../src/model-thinking";
import { streamAnthropic } from "../src/providers/anthropic";
import type { Context, Model, Tool } from "../src/types";

const baseModel: Model<"anthropic-messages"> = {
	id: "claude-sonnet-4-5",
	name: "Claude Sonnet 4.5",
	api: "anthropic-messages",
	provider: "anthropic",
	baseUrl:
		"https://us-east5-aiplatform.googleapis.com/v1/projects/example/locations/us-east5/publishers/anthropic/models/claude-sonnet-4-5:streamRawPredict",
	reasoning: false,
	input: ["text"],
	cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
	contextWindow: 200_000,
	maxTokens: 8_192,
};

const bashTool: Tool = {
	name: "bash",
	description: "run a bash command",
	parameters: {
		type: "object",
		properties: { command: { type: "string" } },
		required: ["command"],
	} as unknown as Tool["parameters"],
};

const baseContext: Context = {
	systemPrompt: ["Stay concise."],
	messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
	tools: [bashTool],
};

function abortedSignal(): AbortSignal {
	const controller = new AbortController();
	controller.abort();
	return controller.signal;
}

function captureParams(
	model: Model<"anthropic-messages">,
): Promise<{ tools?: Array<{ name: string; strict?: unknown }> }> {
	const { promise, resolve } = Promise.withResolvers<{ tools?: Array<{ name: string; strict?: unknown }> }>();
	void streamAnthropic(model, baseContext, {
		apiKey: "sk-ant-api-test",
		isOAuth: false,
		signal: abortedSignal(),
		onPayload: payload => {
			resolve(payload as { tools?: Array<{ name: string; strict?: unknown }> });
			return undefined;
		},
	});
	return promise;
}

describe("issue #826: Anthropic strict-tools opt-out for Vertex-style proxies", () => {
	it("preserves strict:true on allowlisted tools by default (api.anthropic.com baseline)", async () => {
		const params = await captureParams(baseModel);
		const bash = params.tools?.find(t => t.name === "bash");
		expect(bash).toBeDefined();
		expect(bash?.strict).toBe(true);
	});

	it("omits strict on tool defs when compat.disableStrictTools is set", async () => {
		const params = await captureParams({
			...baseModel,
			compat: { disableStrictTools: true },
		});
		const bash = params.tools?.find(t => t.name === "bash");
		expect(bash).toBeDefined();
		expect(bash?.strict).toBeUndefined();
	});

	it("preserves adaptive thinking by default", async () => {
		const adaptiveModel: Model<"anthropic-messages"> = {
			...baseModel,
			id: "claude-opus-4-7",
			reasoning: true,
			thinking: {
				mode: "anthropic-adaptive",
				minLevel: Effort.Minimal,
				maxLevel: Effort.XHigh,
			},
		};
		const { promise, resolve } = Promise.withResolvers<{ thinking?: { type?: string } }>();
		void streamAnthropic(adaptiveModel, baseContext, {
			apiKey: "sk-ant-api-test",
			isOAuth: false,
			signal: abortedSignal(),
			thinkingEnabled: true,
			onPayload: payload => {
				resolve(payload as { thinking?: { type?: string } });
				return undefined;
			},
		});
		const params = await promise;
		expect(params.thinking?.type).toBe("adaptive");
	});

	it("maps adaptive thinking to enabled when compat.disableAdaptiveThinking is set", async () => {
		const adaptiveModel: Model<"anthropic-messages"> = {
			...baseModel,
			id: "claude-opus-4-7",
			reasoning: true,
			thinking: {
				mode: "anthropic-adaptive",
				minLevel: Effort.Minimal,
				maxLevel: Effort.XHigh,
			},
			compat: { disableAdaptiveThinking: true },
		};
		const { promise, resolve } = Promise.withResolvers<{ thinking?: { type?: string; budget_tokens?: number } }>();
		void streamAnthropic(adaptiveModel, baseContext, {
			apiKey: "sk-ant-api-test",
			isOAuth: false,
			signal: abortedSignal(),
			thinkingEnabled: true,
			onPayload: payload => {
				resolve(payload as { thinking?: { type?: string; budget_tokens?: number } });
				return undefined;
			},
		});
		const params = await promise;
		expect(params.thinking?.type).toBe("enabled");
		expect(typeof params.thinking?.budget_tokens).toBe("number");
	});
});