import assert from "node:assert/strict";
import type { SdkAgentEvent } from "@singularity-forge/rpc-client";
import { describe, it } from "vitest";
import {
	formatBlocker,
	formatCompletion,
	formatCostUpdate,
	formatError,
	formatEvent,
	formatGenericEvent,
	formatMessage,
	formatSessionStarted,
	formatTaskTransition,
	formatToolEnd,
	formatToolStart,
} from "./event-formatter.js";
import type { FormattedEvent, PendingBlocker } from "./types.js";

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function embedColor(fe: FormattedEvent): number | null {
	return fe.embed?.data.color ?? null;
}

function embedTitle(fe: FormattedEvent): string | undefined {
	return fe.embed?.data.title;
}

function embedDescription(fe: FormattedEvent): string | undefined {
	return fe.embed?.data.description;
}

// ---------------------------------------------------------------------------
// formatToolStart
// ---------------------------------------------------------------------------

describe("formatToolStart", () => {
	it("produces grey embed with tool name", () => {
		const result = formatToolStart({
			type: "tool_execution_start",
			name: "read_file",
		});
		assert.ok(result.content.includes("read_file"));
		assert.equal(embedColor(result), 0x95a5a6); // grey
		assert.ok(embedTitle(result)?.includes("read_file"));
	});

	it("handles missing name gracefully", () => {
		const result = formatToolStart({ type: "tool_execution_start" });
		assert.ok(result.content.includes("unknown"));
	});

	it("includes input in description when present", () => {
		const result = formatToolStart({
			type: "tool_execution_start",
			name: "bash",
			input: "ls -la",
		});
		assert.ok(embedDescription(result)?.includes("ls -la"));
	});
});

// ---------------------------------------------------------------------------
// formatToolEnd
// ---------------------------------------------------------------------------

describe("formatToolEnd", () => {
	it("shows success icon for normal completion", () => {
		const result = formatToolEnd({
			type: "tool_execution_end",
			name: "read_file",
			output: "done",
		});
		assert.ok(result.content.includes("✅"));
		assert.equal(embedColor(result), 0x95a5a6); // grey
	});

	it("shows error icon and red color for errored tool", () => {
		const result = formatToolEnd({
			type: "tool_execution_end",
			name: "bash",
			isError: true,
		});
		assert.ok(result.content.includes("❌"));
		assert.equal(embedColor(result), 0xe74c3c); // red
	});

	it("includes duration when present", () => {
		const result = formatToolEnd({
			type: "tool_execution_end",
			name: "bash",
			duration: 3500,
		});
		assert.ok(result.embed?.data.footer?.text?.includes("3.5s"));
	});
});

// ---------------------------------------------------------------------------
// formatMessage
// ---------------------------------------------------------------------------

describe("formatMessage", () => {
	it("extracts text from content blocks", () => {
		const result = formatMessage({
			type: "message",
			content: [{ type: "text", text: "Hello world" }],
		});
		assert.ok(embedDescription(result)?.includes("Hello world"));
		assert.equal(embedColor(result), 0x3498db); // blue
	});

	it("falls back to message field when content is a string", () => {
		const result = formatMessage({ type: "message", message: "plain text" });
		assert.ok(embedDescription(result)?.includes("plain text"));
	});

	it("handles empty content blocks", () => {
		const result = formatMessage({ type: "message", content: [] });
		assert.ok(result.content.includes("empty message"));
		assert.equal(result.embed, undefined);
	});

	it("handles null content gracefully", () => {
		const result = formatMessage({ type: "message" });
		assert.ok(result.content.includes("empty message"));
	});
});

// ---------------------------------------------------------------------------
// formatBlocker — select
// ---------------------------------------------------------------------------

describe("formatBlocker", () => {
	it("produces ActionRow with numbered buttons for select", () => {
		const blocker: PendingBlocker = {
			id: "req-1",
			method: "select",
			message: "Choose an option",
			event: {
				type: "extension_ui_request",
				id: "req-1",
				method: "select",
				title: "Choose",
				options: ["Option A", "Option B", "Option C"],
			},
		};

		const result = formatBlocker(blocker, "12345");
		assert.ok(result.content.includes("<@12345>"));
		assert.equal(embedColor(result), 0xf1c40f); // yellow
		assert.ok(result.components);
		assert.ok(result.components!.length > 0);

		// Check buttons
		const row = result.components![0];
		const buttons = row.components;
		assert.equal(buttons.length, 3);
	});

	it("handles empty options array for select", () => {
		const blocker: PendingBlocker = {
			id: "req-2",
			method: "select",
			message: "Pick one",
			event: {
				type: "extension_ui_request",
				id: "req-2",
				method: "select",
				title: "Pick",
				options: [],
			},
		};

		const result = formatBlocker(blocker, "12345");
		// No components when no options
		assert.equal(result.components, undefined);
		// Embed should show 'No options'
		const fields = result.embed?.data.fields;
		assert.ok(fields?.some((f) => f.value.includes("No options")));
	});

	it("produces Yes/No buttons for confirm", () => {
		const blocker: PendingBlocker = {
			id: "req-3",
			method: "confirm",
			message: "Are you sure?",
			event: {
				type: "extension_ui_request",
				id: "req-3",
				method: "confirm",
				title: "Confirm",
				message: "This will delete everything",
			},
		};

		const result = formatBlocker(blocker, "99999");
		assert.ok(result.components);
		assert.equal(result.components!.length, 1);
		const buttons = result.components![0].components;
		assert.equal(buttons.length, 2);
	});

	it("produces text instructions for input method", () => {
		const blocker: PendingBlocker = {
			id: "req-4",
			method: "input",
			message: "Enter your name",
			event: {
				type: "extension_ui_request",
				id: "req-4",
				method: "input",
				title: "Name",
				placeholder: "John Doe",
			},
		};

		const result = formatBlocker(blocker, "12345");
		// No interactive buttons for input — text instructions only
		assert.equal(result.components, undefined);
		const fields = result.embed?.data.fields;
		assert.ok(fields?.some((f) => f.value.includes("Reply in this channel")));
	});

	it("produces text instructions for editor method", () => {
		const blocker: PendingBlocker = {
			id: "req-5",
			method: "editor",
			message: "Edit the config",
			event: {
				type: "extension_ui_request",
				id: "req-5",
				method: "editor",
				title: "Config",
				prefill: "key: value",
			},
		};

		const result = formatBlocker(blocker, "12345");
		assert.equal(result.components, undefined);
		const fields = result.embed?.data.fields;
		assert.ok(fields?.some((f) => f.value.includes("Reply in this channel")));
		assert.ok(fields?.some((f) => f.value.includes("key: value")));
	});
});

// ---------------------------------------------------------------------------
// formatCompletion
// ---------------------------------------------------------------------------

describe("formatCompletion", () => {
	it("shows green for completed", () => {
		const result = formatCompletion({
			type: "execution_complete",
			status: "completed",
		});
		assert.equal(embedColor(result), 0x2ecc71); // green
		assert.ok(result.content.includes("🏁"));
	});

	it("shows red for error status", () => {
		const result = formatCompletion({
			type: "execution_complete",
			status: "error",
			reason: "Out of tokens",
		});
		assert.equal(embedColor(result), 0xe74c3c); // red
		assert.ok(embedDescription(result)?.includes("Out of tokens"));
	});

	it("includes stats when present", () => {
		const result = formatCompletion({
			type: "execution_complete",
			status: "completed",
			stats: { cost: 0.42, tokens: { total: 10000 } },
		});
		const fields = result.embed?.data.fields;
		assert.ok(fields?.some((f) => f.value.includes("$0.42")));
		assert.ok(fields?.some((f) => f.value.includes("10,000")));
	});
});

// ---------------------------------------------------------------------------
// formatError
// ---------------------------------------------------------------------------

describe("formatError", () => {
	it("includes session ID and error message", () => {
		const result = formatError("sess-abc", "Connection refused");
		assert.equal(embedColor(result), 0xe74c3c); // red
		assert.ok(embedDescription(result)?.includes("Connection refused"));
		assert.ok(result.embed?.data.footer?.text?.includes("sess-abc"));
	});
});

// ---------------------------------------------------------------------------
// formatCostUpdate
// ---------------------------------------------------------------------------

describe("formatCostUpdate", () => {
	it("formats cumulative cost", () => {
		const result = formatCostUpdate({
			type: "cost_update",
			cumulativeCost: 1.23,
			tokens: { input: 5000, output: 2000 },
		});
		assert.ok(result.content.includes("$1.23"));
		assert.equal(embedColor(result), 0x3498db); // blue
	});

	it("handles zero cost", () => {
		const result = formatCostUpdate({
			type: "cost_update",
			cumulativeCost: 0,
			tokens: { input: 0, output: 0 },
		});
		assert.ok(result.content.includes("$0.0000"));
	});
});

// ---------------------------------------------------------------------------
// formatSessionStarted
// ---------------------------------------------------------------------------

describe("formatSessionStarted", () => {
	it("includes project name", () => {
		const result = formatSessionStarted("my-project");
		assert.ok(result.content.includes("my-project"));
		assert.ok(embedDescription(result)?.includes("my-project"));
		assert.equal(embedColor(result), 0x3498db); // blue
	});
});

// ---------------------------------------------------------------------------
// formatTaskTransition
// ---------------------------------------------------------------------------

describe("formatTaskTransition", () => {
	it("shows complete icon for completed tasks", () => {
		const result = formatTaskTransition({
			type: "task_transition",
			taskId: "T01",
			sliceId: "S01",
			status: "complete",
		});
		assert.ok(result.content.includes("✅"));
		assert.equal(embedColor(result), 0x2ecc71); // green
	});

	it("shows error icon for errored tasks", () => {
		const result = formatTaskTransition({
			type: "task_transition",
			taskId: "T02",
			status: "error",
		});
		assert.ok(result.content.includes("❌"));
		assert.equal(embedColor(result), 0xe74c3c); // red
	});
});

// ---------------------------------------------------------------------------
// formatGenericEvent
// ---------------------------------------------------------------------------

describe("formatGenericEvent", () => {
	it("renders unknown event type as grey embed", () => {
		const result = formatGenericEvent({
			type: "some_custom_event",
			data: "hello",
		});
		assert.equal(embedColor(result), 0x95a5a6); // grey
		assert.ok(embedTitle(result)?.includes("some_custom_event"));
	});

	it("handles events with no extra fields", () => {
		const result = formatGenericEvent({ type: "bare_event" });
		assert.ok(result.content.includes("bare_event"));
	});
});

// ---------------------------------------------------------------------------
// formatEvent — dispatch
// ---------------------------------------------------------------------------

describe("formatEvent", () => {
	it("dispatches tool_execution_start", () => {
		const result = formatEvent({ type: "tool_execution_start", name: "read" });
		assert.ok(result.content.includes("🔧"));
	});

	it("dispatches execution_complete", () => {
		const result = formatEvent({
			type: "execution_complete",
			status: "completed",
		});
		assert.ok(result.content.includes("🏁"));
	});

	it("falls back to generic for unknown types", () => {
		const result = formatEvent({ type: "totally_unknown" });
		assert.ok(result.content.includes("📡"));
	});

	it("dispatches cost_update", () => {
		const result = formatEvent({ type: "cost_update", cumulativeCost: 0.5 });
		assert.ok(result.content.includes("💰"));
	});

	it("dispatches message types", () => {
		for (const type of ["message_start", "message_end", "message"]) {
			const result = formatEvent({ type, message: "hi" });
			assert.ok(result.content.includes("💬"), `Failed for type: ${type}`);
		}
	});

	// Negative: missing type field
	it("handles event with missing type gracefully", () => {
		const result = formatEvent({} as SdkAgentEvent);
		assert.ok(result.content); // should not throw
	});

	// Negative: null fields
	it("handles event with null fields gracefully", () => {
		const result = formatEvent({
			type: "tool_execution_start",
			name: null,
		} as unknown as SdkAgentEvent);
		assert.ok(result.content);
	});
});
