/**
 * Trajectory Recorder Tests
 */

import { beforeEach, describe, expect, test } from "vitest";
import { closeDatabase, getDatabase, openDatabase } from "../sf-db.js";
import {
	clearTrajectory,
	ensureTrajectoryTable,
	formatTrajectory,
	getTrajectory,
	getTrajectorySummary,
	recordTrajectoryStep,
	STEP_TYPES,
} from "../trajectory-recorder.js";

describe("Trajectory Recorder", () => {
	let db;
	let sessionId;

	beforeEach(() => {
		closeDatabase();
		openDatabase(":memory:");
		db = getDatabase();
		sessionId = `test-session-${Date.now()}`;
		ensureTrajectoryTable(db);
	});

	test("recordTrajectoryStep_creates_tool_call", () => {
		const result = recordTrajectoryStep({
			sessionId,
			stepType: STEP_TYPES.TOOL_CALL,
			toolName: "readFile",
			toolParams: { path: "src/foo.js" },
			db,
		});
		expect(result).not.toBeNull();
		expect(result.id).toBeGreaterThan(0);
		expect(result.stepNumber).toBe(1);
	});

	test("recordTrajectoryStep_increments_step_number", () => {
		const r1 = recordTrajectoryStep({
			sessionId,
			stepType: STEP_TYPES.TOOL_CALL,
			toolName: "readFile",
			db,
		});
		const r2 = recordTrajectoryStep({
			sessionId,
			stepType: STEP_TYPES.TOOL_RESULT,
			toolName: "readFile",
			toolResult: "content",
			db,
		});
		expect(r1.stepNumber).toBe(1);
		expect(r2.stepNumber).toBe(2);
	});

	test("recordTrajectoryStep_records_error", () => {
		const result = recordTrajectoryStep({
			sessionId,
			stepType: STEP_TYPES.ERROR,
			isError: true,
			errorMessage: "File not found",
			errorType: "FileNotFoundError",
			errorDetails: "Stack trace here",
			db,
		});
		expect(result).not.toBeNull();

		const steps = getTrajectory({ sessionId, db });
		expect(steps[0].isError).toBe(true);
		expect(steps[0].errorMessage).toBe("File not found");
		expect(steps[0].errorType).toBe("FileNotFoundError");
	});

	test("recordTrajectoryStep_records_cost_and_tokens", () => {
		recordTrajectoryStep({
			sessionId,
			stepType: STEP_TYPES.LLM_CALL,
			llmModel: "claude-3-opus",
			inputTokens: 1000,
			outputTokens: 500,
			stepCost: 0.015,
			db,
		});

		const steps = getTrajectory({ sessionId, db });
		expect(steps[0].inputTokens).toBe(1000);
		expect(steps[0].outputTokens).toBe(500);
		expect(steps[0].stepCost).toBe(0.015);
	});

	test("getTrajectory_filters_by_type", () => {
		recordTrajectoryStep({
			sessionId,
			stepType: STEP_TYPES.TOOL_CALL,
			toolName: "a",
			db,
		});
		recordTrajectoryStep({
			sessionId,
			stepType: STEP_TYPES.LLM_CALL,
			llmModel: "x",
			db,
		});
		recordTrajectoryStep({
			sessionId,
			stepType: STEP_TYPES.TOOL_CALL,
			toolName: "b",
			db,
		});

		const toolSteps = getTrajectory({
			sessionId,
			stepType: STEP_TYPES.TOOL_CALL,
			db,
		});
		expect(toolSteps.length).toBe(2);
		expect(toolSteps.every((s) => s.stepType === "tool_call")).toBe(true);
	});

	test("getTrajectorySummary_returns_stats", () => {
		recordTrajectoryStep({
			sessionId,
			stepType: STEP_TYPES.TOOL_CALL,
			toolName: "a",
			inputTokens: 100,
			outputTokens: 50,
			stepCost: 0.01,
			db,
		});
		recordTrajectoryStep({
			sessionId,
			stepType: STEP_TYPES.TOOL_CALL,
			toolName: "b",
			inputTokens: 200,
			outputTokens: 100,
			stepCost: 0.02,
			db,
		});
		recordTrajectoryStep({
			sessionId,
			stepType: STEP_TYPES.ERROR,
			isError: true,
			db,
		});

		const summary = getTrajectorySummary(sessionId, null, db);
		expect(summary.totalSteps).toBe(3);
		expect(summary.errorCount).toBe(1);
		expect(summary.totalInputTokens).toBe(300);
		expect(summary.totalOutputTokens).toBe(150);
		expect(summary.totalCost).toBeCloseTo(0.03, 4);
		expect(summary.uniqueTools).toBe(2);
	});

	test("formatTrajectory_formats_steps", () => {
		recordTrajectoryStep({
			sessionId,
			stepType: STEP_TYPES.TOOL_CALL,
			toolName: "readFile",
			inputTokens: 100,
			stepCost: 0.01,
			db,
		});
		recordTrajectoryStep({
			sessionId,
			stepType: STEP_TYPES.LLM_CALL,
			llmModel: "claude",
			inputTokens: 1000,
			outputTokens: 500,
			stepCost: 0.015,
			db,
		});

		const steps = getTrajectory({ sessionId, db });
		const formatted = formatTrajectory(steps);
		expect(formatted).toContain("Trajectory (2 steps total");
		expect(formatted).toContain("Tool: readFile");
		expect(formatted).toContain("LLM: claude");
	});

	test("clearTrajectory_deletes_steps", () => {
		recordTrajectoryStep({ sessionId, stepType: STEP_TYPES.TOOL_CALL, db });
		const deleted = clearTrajectory(sessionId, db);
		expect(deleted).toBe(1);

		const steps = getTrajectory({ sessionId, db });
		expect(steps.length).toBe(0);
	});

	test("recordTrajectoryStep_rejects_invalid_type", () => {
		const result = recordTrajectoryStep({
			sessionId,
			stepType: "invalid_type",
			db,
		});
		expect(result).toBeNull();
	});

	test("recordTrajectoryStep_requires_sessionId", () => {
		const result = recordTrajectoryStep({
			stepType: STEP_TYPES.TOOL_CALL,
			db,
		});
		expect(result).toBeNull();
	});
});
