import type { AgentEvent } from "@dispatch/kernel";
import { describe, expect, it } from "vitest";
import {
	computeExpectedCacheRate,
	isParseError,
	isReasoningEffortParseError,
	isSinceSeqError,
	isValidReasoningEffort,
	isWindowParamError,
	parseChatBody,
	parseQueueBody,
	parseReasoningEffortBody,
	parseSinceSeq,
	parseWindowParam,
	serializeEventLine,
} from "./logic.js";

describe("parseChatBody", () => {
	const fakeId = () => "test-uuid";

	it("returns error for null body", () => {
		const result = parseChatBody(null, fakeId);
		expect(isParseError(result)).toBe(true);
		if (isParseError(result)) {
			expect(result.error).toContain("JSON object");
		}
	});

	it("returns error for non-object body", () => {
		const result = parseChatBody("hello", fakeId);
		expect(isParseError(result)).toBe(true);
	});

	it("returns error when message is missing", () => {
		const result = parseChatBody({ conversationId: "c1" }, fakeId);
		expect(isParseError(result)).toBe(true);
		if (isParseError(result)) {
			expect(result.error).toContain("message");
		}
	});

	it("returns error when message is empty string", () => {
		const result = parseChatBody({ message: "" }, fakeId);
		expect(isParseError(result)).toBe(true);
	});

	it("returns error when message is whitespace only", () => {
		const result = parseChatBody({ message: "   " }, fakeId);
		expect(isParseError(result)).toBe(true);
	});

	it("returns error when message is not a string", () => {
		const result = parseChatBody({ message: 42 }, fakeId);
		expect(isParseError(result)).toBe(true);
	});

	it("generates conversationId when absent", () => {
		const result = parseChatBody({ message: "hello" }, fakeId);
		expect(isParseError(result)).toBe(false);
		if (!isParseError(result)) {
			expect(result.conversationId).toBe("test-uuid");
			expect(result.message).toBe("hello");
		}
	});

	it("generates conversationId when empty string", () => {
		const result = parseChatBody({ message: "hello", conversationId: "" }, fakeId);
		expect(isParseError(result)).toBe(false);
		if (!isParseError(result)) {
			expect(result.conversationId).toBe("test-uuid");
		}
	});

	it("uses provided conversationId", () => {
		const result = parseChatBody({ message: "hello", conversationId: "my-conv" }, fakeId);
		expect(isParseError(result)).toBe(false);
		if (!isParseError(result)) {
			expect(result.conversationId).toBe("my-conv");
		}
	});

	it("trims message whitespace", () => {
		const result = parseChatBody({ message: "  hello world  " }, fakeId);
		expect(isParseError(result)).toBe(false);
		if (!isParseError(result)) {
			expect(result.message).toBe("hello world");
		}
	});

	it("extracts model when present", () => {
		const result = parseChatBody({ message: "hi", model: "opencode/m1" }, fakeId);
		expect(isParseError(result)).toBe(false);
		if (!isParseError(result)) {
			expect(result.model).toBe("opencode/m1");
		}
	});

	it("extracts cwd when present", () => {
		const result = parseChatBody({ message: "hi", cwd: "/tmp" }, fakeId);
		expect(isParseError(result)).toBe(false);
		if (!isParseError(result)) {
			expect(result.cwd).toBe("/tmp");
		}
	});

	it("extracts both model and cwd", () => {
		const result = parseChatBody({ message: "hi", model: "openai/gpt-4", cwd: "/home" }, fakeId);
		expect(isParseError(result)).toBe(false);
		if (!isParseError(result)) {
			expect(result.model).toBe("openai/gpt-4");
			expect(result.cwd).toBe("/home");
		}
	});

	it("omits model when absent", () => {
		const result = parseChatBody({ message: "hi" }, fakeId);
		expect(isParseError(result)).toBe(false);
		if (!isParseError(result)) {
			expect(result.model).toBeUndefined();
		}
	});

	it("omits cwd when absent", () => {
		const result = parseChatBody({ message: "hi" }, fakeId);
		expect(isParseError(result)).toBe(false);
		if (!isParseError(result)) {
			expect(result.cwd).toBeUndefined();
		}
	});

	it("returns error when model is not a string", () => {
		const result = parseChatBody({ message: "hi", model: 42 }, fakeId);
		expect(isParseError(result)).toBe(true);
		if (isParseError(result)) {
			expect(result.error).toContain("model");
		}
	});

	it("returns error when cwd is not a string", () => {
		const result = parseChatBody({ message: "hi", cwd: true }, fakeId);
		expect(isParseError(result)).toBe(true);
		if (isParseError(result)) {
			expect(result.error).toContain("cwd");
		}
	});

	it("extracts reasoningEffort when present and valid", () => {
		const result = parseChatBody({ message: "hi", reasoningEffort: "low" }, fakeId);
		expect(isParseError(result)).toBe(false);
		if (!isParseError(result)) {
			expect(result.reasoningEffort).toBe("low");
		}
	});

	it("accepts all valid reasoningEffort levels", () => {
		for (const level of ["low", "medium", "high", "xhigh", "max"]) {
			const result = parseChatBody({ message: "hi", reasoningEffort: level }, fakeId);
			expect(isParseError(result)).toBe(false);
			if (!isParseError(result)) {
				expect(result.reasoningEffort).toBe(level);
			}
		}
	});

	it("returns error for invalid reasoningEffort", () => {
		const result = parseChatBody({ message: "hi", reasoningEffort: "banana" }, fakeId);
		expect(isParseError(result)).toBe(true);
		if (isParseError(result)) {
			expect(result.error).toContain("reasoningEffort");
		}
	});

	it("returns error for non-string reasoningEffort", () => {
		const result = parseChatBody({ message: "hi", reasoningEffort: 42 }, fakeId);
		expect(isParseError(result)).toBe(true);
	});

	it("omits reasoningEffort when absent", () => {
		const result = parseChatBody({ message: "hi" }, fakeId);
		expect(isParseError(result)).toBe(false);
		if (!isParseError(result)) {
			expect(result.reasoningEffort).toBeUndefined();
		}
	});
});

describe("parseSinceSeq", () => {
	it("returns 0 when undefined", () => {
		expect(parseSinceSeq(undefined)).toBe(0);
	});

	it("returns 0 when empty string", () => {
		expect(parseSinceSeq("")).toBe(0);
	});

	it("parses valid non-negative integer", () => {
		expect(parseSinceSeq("0")).toBe(0);
		expect(parseSinceSeq("5")).toBe(5);
		expect(parseSinceSeq("42")).toBe(42);
	});

	it("returns ParseError for non-integer string", () => {
		const result = parseSinceSeq("abc");
		expect(isSinceSeqError(result)).toBe(true);
		if (isSinceSeqError(result)) {
			expect(result.error).toContain("sinceSeq");
		}
	});

	it("returns ParseError for float", () => {
		const result = parseSinceSeq("3.14");
		expect(isSinceSeqError(result)).toBe(true);
	});

	it("returns ParseError for negative integer", () => {
		const result = parseSinceSeq("-1");
		expect(isSinceSeqError(result)).toBe(true);
	});
});

describe("parseWindowParam", () => {
	it("returns undefined (absent) when undefined", () => {
		expect(parseWindowParam(undefined, "limit")).toBeUndefined();
	});

	it("returns undefined (absent) when empty string", () => {
		expect(parseWindowParam("", "limit")).toBeUndefined();
	});

	it("parses a valid positive integer", () => {
		expect(parseWindowParam("1", "limit")).toBe(1);
		expect(parseWindowParam("42", "beforeSeq")).toBe(42);
	});

	it("returns ParseError for zero (store would treat it as absent)", () => {
		const result = parseWindowParam("0", "limit");
		expect(isWindowParamError(result)).toBe(true);
		if (isWindowParamError(result)) {
			expect(result.error).toContain("limit");
			expect(result.error).toContain("positive integer");
		}
	});

	it("returns ParseError for a negative integer", () => {
		expect(isWindowParamError(parseWindowParam("-1", "limit"))).toBe(true);
	});

	it("returns ParseError for a non-integer", () => {
		expect(isWindowParamError(parseWindowParam("1.5", "beforeSeq"))).toBe(true);
	});

	it("returns ParseError for a non-numeric string", () => {
		const result = parseWindowParam("abc", "beforeSeq");
		expect(isWindowParamError(result)).toBe(true);
		if (isWindowParamError(result)) {
			expect(result.error).toContain("beforeSeq");
		}
	});

	it("names the param in the error message", () => {
		const limit = parseWindowParam("0", "limit");
		const before = parseWindowParam("0", "beforeSeq");
		if (isWindowParamError(limit)) expect(limit.error).toContain("limit");
		if (isWindowParamError(before)) expect(before.error).toContain("beforeSeq");
	});

	it("isWindowParamError is false for absent and for a valid number", () => {
		expect(isWindowParamError(undefined)).toBe(false);
		expect(isWindowParamError(5)).toBe(false);
	});
});

describe("serializeEventLine", () => {
	it("serializes an event as JSON followed by newline", () => {
		const event: AgentEvent = {
			type: "text-delta",
			conversationId: "tab1",
			turnId: "turn1",
			delta: "hello",
		};
		const line = serializeEventLine(event);
		expect(line).toBe(`${JSON.stringify(event)}\n`);
	});

	it("serializes a done event", () => {
		const event: AgentEvent = {
			type: "done",
			conversationId: "tab1",
			turnId: "turn1",
			reason: "stop",
		};
		const line = serializeEventLine(event);
		const parsed = JSON.parse(line.trim());
		expect(parsed.type).toBe("done");
		expect(parsed.reason).toBe("stop");
	});
});

describe("computeExpectedCacheRate", () => {
	it("returns round(cacheRead/(cacheRead+cacheWrite)*100)", () => {
		expect(computeExpectedCacheRate(800, 200)).toBe(80);
	});

	it("returns 0 when cacheRead+cacheWrite is 0", () => {
		expect(computeExpectedCacheRate(0, 0)).toBe(0);
	});

	it("returns 100 when all tokens are cacheRead", () => {
		expect(computeExpectedCacheRate(500, 0)).toBe(100);
	});

	it("returns 0 when all tokens are cacheWrite", () => {
		expect(computeExpectedCacheRate(0, 500)).toBe(0);
	});

	it("rounds to nearest integer", () => {
		expect(computeExpectedCacheRate(1, 2)).toBe(33);
		expect(computeExpectedCacheRate(2, 1)).toBe(67);
	});
});

describe("isValidReasoningEffort", () => {
	it("returns true for all valid levels", () => {
		expect(isValidReasoningEffort("low")).toBe(true);
		expect(isValidReasoningEffort("medium")).toBe(true);
		expect(isValidReasoningEffort("high")).toBe(true);
		expect(isValidReasoningEffort("xhigh")).toBe(true);
		expect(isValidReasoningEffort("max")).toBe(true);
	});

	it("returns false for invalid strings", () => {
		expect(isValidReasoningEffort("banana")).toBe(false);
		expect(isValidReasoningEffort("")).toBe(false);
		expect(isValidReasoningEffort("LOW")).toBe(false);
	});

	it("returns false for non-strings", () => {
		expect(isValidReasoningEffort(42)).toBe(false);
		expect(isValidReasoningEffort(null)).toBe(false);
		expect(isValidReasoningEffort(undefined)).toBe(false);
		expect(isValidReasoningEffort(true)).toBe(false);
	});
});

describe("parseReasoningEffortBody", () => {
	it("returns the level for a valid body", () => {
		expect(parseReasoningEffortBody({ reasoningEffort: "low" })).toBe("low");
		expect(parseReasoningEffortBody({ reasoningEffort: "max" })).toBe("max");
	});

	it("returns ParseError for missing reasoningEffort", () => {
		const result = parseReasoningEffortBody({});
		expect(isReasoningEffortParseError(result)).toBe(true);
		if (isReasoningEffortParseError(result)) {
			expect(result.error).toContain("reasoningEffort");
		}
	});

	it("returns ParseError for invalid level", () => {
		const result = parseReasoningEffortBody({ reasoningEffort: "banana" });
		expect(isReasoningEffortParseError(result)).toBe(true);
		if (isReasoningEffortParseError(result)) {
			expect(result.error).toContain("reasoningEffort");
		}
	});

	it("returns ParseError for non-object body", () => {
		expect(isReasoningEffortParseError(parseReasoningEffortBody(null))).toBe(true);
		expect(isReasoningEffortParseError(parseReasoningEffortBody("string"))).toBe(true);
	});
});

describe("parseQueueBody", () => {
	it("returns error for null body", () => {
		const result = parseQueueBody(null);
		expect(isParseError(result)).toBe(true);
		if (isParseError(result)) {
			expect(result.error).toContain("JSON object");
		}
	});

	it("returns error for non-object body", () => {
		const result = parseQueueBody("hello");
		expect(isParseError(result)).toBe(true);
	});

	it("returns error when text is missing", () => {
		const result = parseQueueBody({});
		expect(isParseError(result)).toBe(true);
		if (isParseError(result)) {
			expect(result.error).toContain("text");
		}
	});

	it("returns error when text is empty string", () => {
		const result = parseQueueBody({ text: "" });
		expect(isParseError(result)).toBe(true);
	});

	it("returns error when text is whitespace only", () => {
		const result = parseQueueBody({ text: "   " });
		expect(isParseError(result)).toBe(true);
	});

	it("returns error when text is not a string", () => {
		const result = parseQueueBody({ text: 42 });
		expect(isParseError(result)).toBe(true);
		if (isParseError(result)) {
			expect(result.error).toContain("text");
		}
	});

	it("returns the trimmed text for a valid body", () => {
		const result = parseQueueBody({ text: "hello" });
		expect(isParseError(result)).toBe(false);
		if (!isParseError(result)) {
			expect(result.text).toBe("hello");
		}
	});

	it("trims text whitespace", () => {
		const result = parseQueueBody({ text: "  hello world  " });
		expect(isParseError(result)).toBe(false);
		if (!isParseError(result)) {
			expect(result.text).toBe("hello world");
		}
	});
});
