/**
 * Unit tests for self-report-fixer.js
 *
 * Purpose: verify pattern-based fix detection, confidence scoring,
 * deduplication, and severity categorization work correctly.
 */

import { afterEach, describe, expect, test } from "vitest";
import {
	categorizeBySeverity,
	classifyReportFixes,
	dedupReports,
	generateTriageSummary,
	promoteSelfReportsToBacklog,
} from "../self-report-fixer.js";
import { closeDatabase, listBacklogItems, openDatabase } from "../sf-db.js";

describe("self-report-fixer", () => {
	afterEach(() => {
		closeDatabase();
	});

	test("detects validation-reviewer-rubric fix pattern", () => {
		const report = {
			id: "report-1",
			title: "validation-reviewer lacks rubric",
			description: "The validation-reviewer prompt should document criteria",
			filed_at: "2026-05-06T16:00:00Z",
			repoIdentity: "forge",
			resolvedAt: null,
		};

		const fixes = classifyReportFixes(report);
		expect(fixes.length).toBeGreaterThan(0);
		expect(fixes[0].pattern).toBe("validation-reviewer-rubric");
		expect(fixes[0].confidence).toBeGreaterThanOrEqual(0.85);
	});

	test("detects gate-verdict-clarity fix pattern", () => {
		const report = {
			id: "report-2",
			title: "Gate verdict semantics not documented",
			description: "Gates should clearly explain pass/fail conditions",
			filed_at: "2026-05-06T16:00:00Z",
			repoIdentity: "forge",
			resolvedAt: null,
		};

		const fixes = classifyReportFixes(report);
		expect(fixes.length).toBeGreaterThan(0);
		const verdictFix = fixes.find((f) => f.pattern === "gate-verdict-clarity");
		expect(verdictFix).toBeDefined();
	});

	test("detects env-vars-unvalidated fix pattern", () => {
		const report = {
			id: "report-3",
			title: "Environment variables not validated",
			description:
				"SF_* env vars should be validated at startup to catch config errors",
			filed_at: "2026-05-06T16:00:00Z",
			repoIdentity: "forge",
			resolvedAt: null,
		};

		const fixes = classifyReportFixes(report);
		const envFix = fixes.find((f) => f.pattern === "env-vars-unvalidated");
		expect(envFix).toBeDefined();
	});

	test("returns empty array for non-matching report", () => {
		const report = {
			id: "report-4",
			title: "Some random issue",
			description: "This does not match any pattern",
			filed_at: "2026-05-06T16:00:00Z",
			repoIdentity: "forge",
			resolvedAt: null,
		};

		const fixes = classifyReportFixes(report);
		expect(fixes.length).toBe(0);
	});

	test("deduplicates reports with same normalized issue", () => {
		const reports = [
			{
				id: "report-1",
				title: "Validation reviewer needs rubric",
				description: "Missing criteria documentation",
				filed_at: "2026-05-06T16:00:00Z",
				repoIdentity: "forge",
				resolvedAt: null,
			},
			{
				id: "report-2",
				title: "VALIDATION REVIEWER lacks rubric",
				description: "Criterion documentation missing",
				filed_at: "2026-05-06T17:00:00Z",
				repoIdentity: "forge",
				resolvedAt: null,
			},
		];

		const deduped = dedupReports(reports);
		expect(deduped.length).toBeLessThanOrEqual(reports.length);

		// Both should be grouped under same normalized key
		const groups = deduped;
		expect(groups.some((g) => g.reports && g.reports.length > 1)).toBe(true);
	});

	test("categorizes reports by severity", () => {
		const reports = [
			{
				id: "report-1",
				title: "Validation reviewer lacks rubric",
				description: "Critical: blocks verification",
				filed_at: "2026-05-06T16:00:00Z",
				repoIdentity: "forge",
				resolvedAt: null,
			},
			{
				id: "report-2",
				title: "Minor typo in comment",
				description: "Low: cosmetic issue",
				filed_at: "2026-05-06T16:00:00Z",
				repoIdentity: "forge",
				resolvedAt: null,
			},
		];

		const categorized = categorizeBySeverity(reports);
		expect(categorized.blocker).toBeDefined();
		expect(categorized.warning).toBeDefined();
		expect(categorized.suggestion).toBeDefined();

		// Validation reviewer should be blocker
		const blockers = categorized.blocker;
		expect(
			blockers.some((r) => r.title.toLowerCase().includes("validation")),
		).toBe(true);
	});

	test("generates triage summary from reports", () => {
		const reports = [
			{
				id: "report-1",
				title: "Validation reviewer lacks rubric",
				description: "Gate evaluation needs documented criteria",
				filed_at: "2026-05-06T16:00:00Z",
				repoIdentity: "forge",
				resolvedAt: null,
			},
			{
				id: "report-2",
				title: "Gate verdict semantics unclear",
				description: "Pass/fail conditions not documented",
				filed_at: "2026-05-06T16:00:00Z",
				repoIdentity: "forge",
				resolvedAt: null,
			},
		];

		const summary = generateTriageSummary(reports);
		expect(summary).toBeDefined();
		expect(summary.totalReports).toBe(2);
		expect(summary.highConfidenceFixes).toBeGreaterThanOrEqual(0);
		expect(summary.recommendations).toBeDefined();
		expect(summary.recommendations.length).toBeGreaterThan(0);
	});

	test("scores confidence based on pattern match quality", () => {
		// Exact match should have high confidence
		const exactReport = {
			id: "r1",
			title: "validation-reviewer lacks rubric",
			description: "The validation-reviewer prompt",
			filed_at: "2026-05-06T16:00:00Z",
			repoIdentity: "forge",
			resolvedAt: null,
		};

		const exactFixes = classifyReportFixes(exactReport);
		expect(exactFixes[0].confidence).toBeGreaterThan(0.9);

		// Partial match should have lower confidence
		const partialReport = {
			id: "r2",
			title: "Validator has issues",
			description: "There are problems with validation",
			filed_at: "2026-05-06T16:00:00Z",
			repoIdentity: "forge",
			resolvedAt: null,
		};

		const partialFixes = classifyReportFixes(partialReport);
		// Should be lower confidence or no match
		if (partialFixes.length > 0) {
			expect(partialFixes[0].confidence).toBeLessThan(0.9);
		}
	});

	test("handles multi-line descriptions correctly", () => {
		const report = {
			id: "report-1",
			title: "Validation Issue",
			description: `
				The validation-reviewer prompt is missing:
				- Documentation of pass/fail criteria
				- Examples of rubric application
				- Instructions for edge cases
			`,
			filed_at: "2026-05-06T16:00:00Z",
			repoIdentity: "forge",
			resolvedAt: null,
		};

		const fixes = classifyReportFixes(report);
		// Should still match the pattern despite multi-line text
		expect(fixes.length).toBeGreaterThan(0);
	});

	test("deduplication handles case-insensitive matching", () => {
		const reports = [
			{
				id: "report-1",
				title: "VALIDATION REVIEWER LACKS RUBRIC",
				description: "Missing rubric",
				filed_at: "2026-05-06T16:00:00Z",
				repoIdentity: "forge",
				resolvedAt: null,
			},
			{
				id: "report-2",
				title: "validation reviewer lacks rubric",
				description: "Missing rubric",
				filed_at: "2026-05-06T16:00:00Z",
				repoIdentity: "forge",
				resolvedAt: null,
			},
		];

		const deduped = dedupReports(reports);
		// Should be treated as duplicates
		expect(deduped.length).toBeLessThan(reports.length);
	});

	test("severity categorization prioritizes blockers", () => {
		const reports = [
			{
				id: "r-blocker",
				title:
					"Validation reviewer lacks rubric - BLOCKS ALL VERIFICATION GATES",
				description: "Critical blocker",
				filed_at: "2026-05-06T16:00:00Z",
				repoIdentity: "forge",
				resolvedAt: null,
			},
			{
				id: "r-warning",
				title: "Minor documentation improvement",
				description: "Nice to have",
				filed_at: "2026-05-06T16:00:00Z",
				repoIdentity: "forge",
				resolvedAt: null,
			},
		];

		const categorized = categorizeBySeverity(reports);
		const blockerCount = categorized.blocker?.length ?? 0;
		const warningCount = categorized.warning?.length ?? 0;

		expect(blockerCount + warningCount).toBeGreaterThan(0);
	});

	test("generates actionable recommendations", () => {
		const reports = [
			{
				id: "report-1",
				title: "Validation reviewer lacks rubric",
				description:
					"The gate evaluation should document pass/fail criteria explicitly",
				filed_at: "2026-05-06T16:00:00Z",
				repoIdentity: "forge",
				resolvedAt: null,
			},
		];

		const summary = generateTriageSummary(reports);
		expect(summary.recommendations).toBeDefined();
		expect(summary.recommendations.length).toBeGreaterThan(0);

		// Recommendation should mention the actual action
		const recommendation = summary.recommendations[0];
		expect(recommendation.toLowerCase()).toMatch(
			/rubric|criteria|document|validation/,
		);
	});

	test("handles empty report list gracefully", () => {
		const emptyReports: any[] = [];

		const deduped = dedupReports(emptyReports);
		expect(deduped.length).toBe(0);

		const categorized = categorizeBySeverity(emptyReports);
		expect(categorized).toBeDefined();

		const summary = generateTriageSummary(emptyReports);
		expect(summary.totalReports).toBe(0);
	});

	test("filters out already-resolved reports", () => {
		const reports = [
			{
				id: "report-1",
				title: "Validation reviewer lacks rubric",
				description: "This was already fixed",
				filed_at: "2026-05-06T16:00:00Z",
				repoIdentity: "forge",
				resolvedAt: "2026-05-06T18:00:00Z", // Already resolved
			},
			{
				id: "report-2",
				title: "Gate verdict clarity missing",
				description: "Still open",
				filed_at: "2026-05-06T16:00:00Z",
				repoIdentity: "forge",
				resolvedAt: null, // Still open
			},
		];

		// Should only process open reports
		const openReports = reports.filter((r) => !r.resolvedAt);
		expect(openReports.length).toBe(1);

		const fixes = classifyReportFixes(openReports[0]);
		expect(fixes.length).toBeGreaterThan(0);
	});

	test("provides fix implementation guidance", () => {
		const report = {
			id: "report-1",
			title: "validation-reviewer prompt lacks rubric",
			description: "Gate evaluation needs explicit pass/fail criteria",
			filed_at: "2026-05-06T16:00:00Z",
			repoIdentity: "forge",
			resolvedAt: null,
		};

		const fixes = classifyReportFixes(report);
		expect(fixes[0]).toHaveProperty("fixFunction");
		expect(typeof fixes[0].fixFunction).toBe("function");

		// The fix function should describe what needs to be done
		const fixDescription = fixes[0].fixFunction.toString();
		expect(fixDescription.length).toBeGreaterThan(0);
	});

	test("promoteSelfReportsToBacklog_when_db_available_creates_deduped_items", () => {
		openDatabase(":memory:");
		const reports = [
			{
				id: "report-1",
				title: "runaway guard hard pause",
				description: "Medium severity repeated pause in external repos",
				severity: "medium",
				resolvedAt: null,
			},
			{
				id: "report-2",
				title: "RUNAWAY guard hard pause",
				description: "Medium severity repeated pause in external repos",
				severity: "medium",
				resolvedAt: null,
			},
			{
				id: "report-3",
				title: "low priority style note",
				description: "Low severity note",
				severity: "low",
				resolvedAt: null,
			},
		];

		const result = promoteSelfReportsToBacklog(process.cwd(), reports);

		expect(result.promoted).toHaveLength(1);
		expect(result.updated).toHaveLength(0);
		const items = listBacklogItems();
		expect(items).toHaveLength(1);
		expect(items[0].id).toMatch(/^self-feedback\.[a-f0-9]{12}$/);
		expect(items[0].source).toBe("self-feedback-triage");
		expect(items[0].note).toContain("reports=2");
		expect(items[0].note).toContain("report-1");
	});

	test("promoteSelfReportsToBacklog_when_repeated_is_idempotent", () => {
		openDatabase(":memory:");
		const reports = [
			{
				id: "report-1",
				title: "gap audit orphan command",
				description: "Medium severity repeated orphan command",
				severity: "medium",
				resolvedAt: null,
			},
		];

		const first = promoteSelfReportsToBacklog(process.cwd(), reports);
		const second = promoteSelfReportsToBacklog(process.cwd(), reports);

		expect(first.promoted).toHaveLength(1);
		expect(second.promoted).toHaveLength(0);
		expect(second.updated).toEqual(first.promoted);
		expect(listBacklogItems()).toHaveLength(1);
	});
});
