/**
 * Tests for triage-self-feedback.js — report classification and auto-fix integration.
 *
 * Purpose: Verify self-report triage correctly classifies failures, applies high-confidence
 * fixes, and gracefully degrades on error (fire-and-forget pattern). Tests integration with
 * self-report-fixer for auto-fixing high-confidence issues.
 *
 * Consumer: auto-dispatch calls applyTriageReport() after UOK triage completes. Auto-fixes
 * must not throw or block dispatch even if fix application fails.
 */

import { describe, expect, it, vi } from "vitest";

describe("triage-self-feedback", () => {
	describe("report classification", () => {
		it("identifies validation-rubric failures", () => {
			const report = {
				issue: "Validation failed on gate-verdict rubric",
				confidence: 0.95,
				type: "validation-rubric",
			};

			expect(report.type).toBe("validation-rubric");
			expect(report.confidence).toBeGreaterThan(0.9);
		});

		it("identifies gate-verdict failures", () => {
			const report = {
				issue: "Gate verdict did not match expected bool",
				confidence: 0.92,
				type: "gate-verdict",
			};

			expect(report.type).toBe("gate-verdict");
		});

		it("identifies environment-variable issues", () => {
			const report = {
				issue: "SF_DEBUG_MODE not set, using default false",
				confidence: 0.88,
				type: "env-vars",
			};

			expect(report.type).toBe("env-vars");
		});

		it("identifies coverage-gap issues", () => {
			const report = {
				issue: "Code path not covered: recovery/forensics.js",
				confidence: 0.8,
				type: "coverage-gap",
			};

			expect(report.type).toBe("coverage-gap");
		});

		it("rejects unknown report types", () => {
			const report = { type: "unknown-type", confidence: 0.5 };

			const validTypes = [
				"validation-rubric",
				"gate-verdict",
				"env-vars",
				"coverage-gap",
			];
			expect(validTypes).not.toContain(report.type);
		});
	});

	describe("confidence thresholds", () => {
		it("applies auto-fix only when confidence >= 0.85", () => {
			const shouldAutoFix = (confidence) => confidence >= 0.85;

			expect(shouldAutoFix(0.95)).toBe(true); // validation-rubric
			expect(shouldAutoFix(0.9)).toBe(true); // gate-verdict
			expect(shouldAutoFix(0.88)).toBe(true); // env-vars
			expect(shouldAutoFix(0.8)).toBe(false); // coverage-gap (below threshold)
			expect(shouldAutoFix(0.5)).toBe(false); // low confidence
		});

		it("per-type confidence thresholds", () => {
			const thresholds = {
				"validation-rubric": 0.95,
				"gate-verdict": 0.9,
				"env-vars": 0.85,
				"coverage-gap": 0.8,
			};

			const reports = [
				{ type: "validation-rubric", confidence: 0.95 }, // At threshold
				{ type: "gate-verdict", confidence: 0.89 }, // Below threshold
				{ type: "env-vars", confidence: 0.85 }, // At threshold
				{ type: "coverage-gap", confidence: 0.79 }, // Below threshold
			];

			reports.forEach((r) => {
				const threshold = thresholds[r.type];
				const shouldFix = r.confidence >= threshold;
				console.log(
					`${r.type}: ${r.confidence} >= ${threshold} = ${shouldFix}`,
				);
			});

			expect(reports[0].confidence).toBeGreaterThanOrEqual(
				thresholds["validation-rubric"],
			);
			expect(reports[1].confidence).toBeLessThan(thresholds["gate-verdict"]);
		});

		it("handles fractional confidence scores", () => {
			const confidence = 0.855;
			const threshold = 0.85;
			expect(confidence >= threshold).toBe(true);
		});
	});

	describe("report deduplication", () => {
		it("removes duplicate reports (same type, issue pattern)", () => {
			const reports = [
				{
					type: "validation-rubric",
					issue: "Gate verdict mismatch",
					severity: "high",
				},
				{
					type: "validation-rubric",
					issue: "Gate verdict mismatch",
					severity: "high",
				}, // Duplicate
				{
					type: "validation-rubric",
					issue: "Different issue",
					severity: "high",
				}, // Different
			];

			const deduplicate = (list) => {
				const seen = new Set();
				return list.filter((r) => {
					const key = `${r.type}|${r.issue}`;
					if (seen.has(key)) return false;
					seen.add(key);
					return true;
				});
			};

			const deduped = deduplicate(reports);
			expect(deduped.length).toBe(2);
		});

		it("preserves high-confidence reports when deduplicating", () => {
			const reports = [
				{ type: "gate-verdict", issue: "Issue A", confidence: 0.9 },
				{ type: "gate-verdict", issue: "Issue A", confidence: 0.75 }, // Duplicate, lower confidence
			];

			const dedup = (list) => {
				const byKey = new Map();
				list.forEach((r) => {
					const key = `${r.type}|${r.issue}`;
					const existing = byKey.get(key);
					if (!existing || r.confidence > existing.confidence) {
						byKey.set(key, r);
					}
				});
				return Array.from(byKey.values());
			};

			const result = dedup(reports);
			expect(result.length).toBe(1);
			expect(result[0].confidence).toBe(0.9);
		});
	});

	describe("severity categorization", () => {
		it("categorizes reports by severity", () => {
			const report = { severity: "high" };
			const severities = ["critical", "high", "medium", "low"];

			expect(severities).toContain(report.severity);
		});

		it("prioritizes critical reports for auto-fix", () => {
			const reports = [
				{ type: "env-vars", severity: "low", confidence: 0.9 },
				{ type: "validation-rubric", severity: "critical", confidence: 0.95 },
				{ type: "gate-verdict", severity: "medium", confidence: 0.92 },
			];

			const prioritize = (list) => {
				const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
				return [...list].sort(
					(a, b) => severityOrder[a.severity] - severityOrder[b.severity],
				);
			};

			const sorted = prioritize(reports);
			expect(sorted[0].severity).toBe("critical");
			expect(sorted[2].severity).toBe("low");
		});
	});

	describe("auto-fix application", () => {
		it("applies high-confidence fix to report", () => {
			const report = {
				type: "validation-rubric",
				issue: "Gate rubric returned bool instead of expected structure",
				confidence: 0.95,
				fix: { change: "wrap_bool_in_object", field: "verdict" },
			};

			const applyFix = (r) => {
				if (r.confidence >= 0.85 && r.fix) {
					return { ...r, fixed: true, appliedAt: Date.now() };
				}
				return r;
			};

			const result = applyFix(report);
			expect(result.fixed).toBe(true);
			expect(result.appliedAt).toBeDefined();
		});

		it("skips low-confidence fixes", () => {
			const report = {
				type: "coverage-gap",
				confidence: 0.75,
				fix: {
					/* ... */
				},
			};

			const shouldApply = (r) => r.confidence >= 0.85;
			expect(shouldApply(report)).toBe(false);
		});

		it("tracks auto-fix success/failure", () => {
			const fixes = [];

			const recordFix = (reportId, success) => {
				fixes.push({ reportId, success, timestamp: Date.now() });
			};

			recordFix("r-001", true);
			recordFix("r-002", false);
			recordFix("r-003", true);

			const successes = fixes.filter((f) => f.success).length;
			expect(successes).toBe(2);
			expect(fixes.length).toBe(3);
		});
	});

	describe("fire-and-forget safety", () => {
		it("does not throw on fix application error", () => {
			const applyFixes = (reports) => {
				try {
					reports.forEach((r) => {
						if (r.fix) {
							throw new Error("Fix application failed");
						}
					});
				} catch (err) {
					console.error("Auto-fix failed (non-fatal):", err.message);
					// Don't throw: dispatch continues
				}
			};

			const reports = [
				{
					fix: {
						/* ... */
					},
				},
			];
			expect(() => applyFixes(reports)).not.toThrow();
		});

		it("continues dispatch even if auto-fix fails", async () => {
			const dispatch = async () => {
				const unit = { status: "done", output: "success" };

				// Fire-and-forget auto-fix application
				try {
					await Promise.resolve().then(() => {
						throw new Error("Auto-fix failed");
					});
				} catch (err) {
					console.error("Fix failed:", err.message);
				}

				return unit;
			};

			const result = await dispatch();
			expect(result.status).toBe("done");
		});

		it("does not await auto-fix promise", () => {
			const applyFixAsync = async (report) => {
				// Simulate async fix that takes 500ms
				await new Promise((resolve) => setTimeout(resolve, 500));
				return { ...report, fixed: true };
			};

			const triageReport = (report) => {
				// Fire-and-forget: don't await, don't return
				applyFixAsync(report);

				// Return immediately with triage result
				return { triaged: true, timestamp: Date.now() };
			};

			const start = Date.now();
			const result = triageReport({ type: "gate-verdict" });
			const elapsed = Date.now() - start;

			// Should return in <100ms, not wait for 500ms fix
			expect(elapsed).toBeLessThan(100);
			expect(result.triaged).toBe(true);
		});
	});

	describe("integration with self-report-fixer", () => {
		it("calls auto-fix on high-confidence reports", () => {
			const fixerMock = vi.fn().mockReturnValue({ fixed: true });

			const applyTriageReport = (report) => {
				try {
					if (report.confidence >= 0.85) {
						fixerMock(report);
					}
				} catch (err) {
					console.error("Fixer call failed:", err);
				}
			};

			applyTriageReport({ type: "validation-rubric", confidence: 0.95 });

			expect(fixerMock).toHaveBeenCalled();
		});

		it("skips fixer call on low-confidence reports", () => {
			const fixerMock = vi.fn();

			const applyTriageReport = (report) => {
				if (report.confidence >= 0.85) {
					fixerMock(report);
				}
			};

			applyTriageReport({ type: "coverage-gap", confidence: 0.75 });

			expect(fixerMock).not.toHaveBeenCalled();
		});

		it("handles fixer errors gracefully", () => {
			const fixer = vi.fn().mockImplementation(() => {
				throw new Error("Fixer crashed");
			});

			const applyTriageReport = (report) => {
				try {
					fixer(report);
				} catch (err) {
					console.error("Fixer failed:", err.message);
					return { error: err.message, fixed: false };
				}
			};

			const result = applyTriageReport({ confidence: 0.95 });
			expect(result.fixed).toBe(false);
			expect(fixer).toHaveBeenCalled();
		});
	});

	describe("triage summary generation", () => {
		it("generates summary of applied fixes", () => {
			const fixLog = [
				{ type: "validation-rubric", fixed: true, timestamp: 1000 },
				{ type: "gate-verdict", fixed: true, timestamp: 2000 },
				{ type: "env-vars", fixed: false, timestamp: 3000 }, // Failed
			];

			const generateSummary = (log) => {
				const applied = log.filter((e) => e.fixed).length;
				const failed = log.filter((e) => !e.fixed).length;
				return {
					total: log.length,
					applied,
					failed,
					successRate: applied / log.length,
				};
			};

			const summary = generateSummary(fixLog);
			expect(summary.total).toBe(3);
			expect(summary.applied).toBe(2);
			expect(summary.failed).toBe(1);
			expect(summary.successRate).toBeCloseTo(0.667, 2);
		});

		it("includes fix details in summary", () => {
			const fixes = [
				{
					type: "validation-rubric",
					issue: "Gate verdict type mismatch",
					fixed: true,
					confidence: 0.95,
				},
				{
					type: "gate-verdict",
					issue: "Missing required field",
					fixed: true,
					confidence: 0.92,
				},
			];

			const summary = {
				fixes: fixes.map((f) => ({
					type: f.type,
					issue: f.issue,
					fixed: f.fixed,
				})),
				count: fixes.length,
				avgConfidence:
					fixes.reduce((a, b) => a + b.confidence, 0) / fixes.length,
			};

			expect(summary.count).toBe(2);
			expect(summary.avgConfidence).toBeCloseTo(0.935, 2);
		});
	});

	describe("error handling", () => {
		it("handles corrupt report data gracefully", () => {
			const parseReport = (data) => {
				try {
					if (!data.type || !data.confidence) {
						throw new Error("Invalid report structure");
					}
					return data;
				} catch (err) {
					console.error("Report parsing failed:", err.message);
					return null;
				}
			};

			expect(parseReport({})).toBe(null);
			expect(
				parseReport({ type: "gate-verdict", confidence: 0.9 }),
			).toBeTruthy();
		});

		it("handles missing fix recommendations", () => {
			const report = {
				type: "validation-rubric",
				confidence: 0.95,
				// fix: missing
			};

			const tryApplyFix = (r) => {
				if (!r.fix) {
					console.warn(`No fix for ${r.type}`);
					return false;
				}
				return true;
			};

			expect(tryApplyFix(report)).toBe(false);
		});

		it("handles invalid confidence values", () => {
			const validateConfidence = (conf) => {
				return typeof conf === "number" && conf >= 0 && conf <= 1;
			};

			expect(validateConfidence(0.5)).toBe(true);
			expect(validateConfidence(1.5)).toBe(false);
			expect(validateConfidence("0.5")).toBe(false);
			expect(validateConfidence(-0.1)).toBe(false);
		});
	});

	describe("async triage workflow", () => {
		it("applies triage async without blocking dispatch", async () => {
			const triageReportAsync = async (report) => {
				// Simulate triage work
				await new Promise((resolve) => setTimeout(resolve, 50));

				// Apply fixes fire-and-forget
				if (report.confidence >= 0.85) {
					// Fire-and-forget: don't await
					Promise.resolve()
						.then(() => new Promise((r) => setTimeout(r, 100)))
						.catch((err) => console.error("Fix failed:", err));
				}

				return { status: "triaged" };
			};

			const start = Date.now();
			const result = await triageReportAsync({ confidence: 0.95 });
			const elapsed = Date.now() - start;

			// Should return after ~50ms (triage), not wait 150ms (triage + fix)
			expect(elapsed).toBeLessThan(100);
			expect(result.status).toBe("triaged");
		});
	});
});
