import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { mkdir, rm } from "fs/promises";
import { join } from "path";
import { tmpdir } from "os";
import { loadFeedbackDatabase, submitFeedback, getFeedbackForFinding, getFeedbackForRule, hasFeedback, getSuppressionSuggestions, generateFeedbackReport, FP_REASON_DESCRIPTIONS, } from "../../scanners/fp-feedback.js";
// Mock the fp-tracker module to avoid side effects
vi.mock("../../scanners/fp-tracker.js", () => ({
    markFalsePositive: vi.fn().mockResolvedValue(undefined),
    markTruePositive: vi.fn().mockResolvedValue(undefined),
}));
describe("fp-feedback", () => {
    let testDir;
    beforeEach(async () => {
        testDir = join(tmpdir(), `fp-feedback-test-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
        await mkdir(testDir, { recursive: true });
    });
    afterEach(async () => {
        try {
            await rm(testDir, { recursive: true, force: true });
        }
        catch {
            // Ignore cleanup errors
        }
        vi.clearAllMocks();
    });
    const createFinding = (overrides = {}) => ({
        scanner: "semgrep",
        ruleId: "security.sql-injection",
        file: "src/db.ts",
        line: 10,
        message: "SQL injection vulnerability",
        severity: "high",
        confidence: 100,
        ...overrides,
    });
    describe("FP_REASON_DESCRIPTIONS", () => {
        it("has descriptions for all reasons", () => {
            const reasons = [
                "test-code",
                "false-pattern-match",
                "sanitized-elsewhere",
                "intentional",
                "vendor-code",
                "generated-code",
                "example-code",
                "configuration",
                "other",
            ];
            for (const reason of reasons) {
                expect(FP_REASON_DESCRIPTIONS[reason]).toBeDefined();
            }
        });
    });
    describe("loadFeedbackDatabase", () => {
        it("returns empty database for new project", async () => {
            const db = await loadFeedbackDatabase(testDir);
            expect(db.version).toBe("1.0.0");
            expect(db.entries).toHaveLength(0);
            expect(db.stats.totalFeedback).toBe(0);
        });
    });
    describe("submitFeedback", () => {
        it("submits FP feedback correctly", async () => {
            const finding = createFinding();
            const entry = await submitFeedback(testDir, finding, "fp", {
                reason: "test-code",
                details: "This is test code",
            });
            expect(entry.id).toMatch(/^fb-/);
            expect(entry.verdict).toBe("fp");
            expect(entry.reason).toBe("test-code");
            expect(entry.details).toBe("This is test code");
        });
        it("submits TP feedback correctly", async () => {
            const finding = createFinding();
            const entry = await submitFeedback(testDir, finding, "tp");
            expect(entry.verdict).toBe("tp");
            expect(entry.reason).toBeUndefined();
        });
        it("updates stats on submission", async () => {
            const finding = createFinding();
            await submitFeedback(testDir, finding, "fp", { reason: "test-code" });
            await submitFeedback(testDir, finding, "tp");
            const db = await loadFeedbackDatabase(testDir);
            expect(db.stats.totalFeedback).toBe(2);
            expect(db.stats.fpCount).toBe(1);
            expect(db.stats.tpCount).toBe(1);
            expect(db.stats.byReason["test-code"]).toBe(1);
        });
        it("tracks scanner stats", async () => {
            const semgrepFinding = createFinding({ scanner: "semgrep" });
            const eslintFinding = createFinding({ scanner: "eslint" });
            await submitFeedback(testDir, semgrepFinding, "fp", { reason: "test-code" });
            await submitFeedback(testDir, eslintFinding, "fp", { reason: "test-code" });
            await submitFeedback(testDir, semgrepFinding, "tp");
            const db = await loadFeedbackDatabase(testDir);
            expect(db.stats.byScanner["semgrep"]).toEqual({ tp: 1, fp: 1 });
            expect(db.stats.byScanner["eslint"]).toEqual({ tp: 0, fp: 1 });
        });
    });
    describe("getFeedbackForFinding", () => {
        it("returns matching feedback entries", async () => {
            const finding1 = createFinding({ file: "src/a.ts", line: 10 });
            const finding2 = createFinding({ file: "src/b.ts", line: 20 });
            await submitFeedback(testDir, finding1, "fp", { reason: "test-code" });
            await submitFeedback(testDir, finding2, "tp");
            const feedback = await getFeedbackForFinding(testDir, "semgrep", "security.sql-injection", "src/a.ts", 10);
            expect(feedback).toHaveLength(1);
            expect(feedback[0].verdict).toBe("fp");
        });
        it("returns empty array for no matches", async () => {
            const feedback = await getFeedbackForFinding(testDir, "semgrep", "nonexistent", "src/a.ts");
            expect(feedback).toHaveLength(0);
        });
    });
    describe("getFeedbackForRule", () => {
        it("returns all feedback for a rule", async () => {
            const finding1 = createFinding({ file: "src/a.ts" });
            const finding2 = createFinding({ file: "src/b.ts" });
            const finding3 = createFinding({ ruleId: "other-rule" });
            await submitFeedback(testDir, finding1, "fp", { reason: "test-code" });
            await submitFeedback(testDir, finding2, "tp");
            await submitFeedback(testDir, finding3, "fp", { reason: "test-code" });
            const feedback = await getFeedbackForRule(testDir, "semgrep", "security.sql-injection");
            expect(feedback).toHaveLength(2);
        });
    });
    describe("hasFeedback", () => {
        it("returns true when feedback exists", async () => {
            const finding = createFinding();
            await submitFeedback(testDir, finding, "fp", { reason: "test-code" });
            const result = await hasFeedback(testDir, "semgrep", "security.sql-injection", "src/db.ts", 10);
            expect(result).toBe(true);
        });
        it("returns false when no feedback exists", async () => {
            const result = await hasFeedback(testDir, "semgrep", "security.sql-injection", "src/db.ts");
            expect(result).toBe(false);
        });
    });
    describe("getSuppressionSuggestions", () => {
        it("returns suggestions for high FP rate rules", async () => {
            // Submit 10 FPs for one rule
            for (let i = 0; i < 10; i++) {
                const finding = createFinding({ file: `src/file${i}.ts`, line: i });
                await submitFeedback(testDir, finding, "fp", { reason: "test-code" });
            }
            const suggestions = await getSuppressionSuggestions(testDir, {
                minFPRate: 0.5,
                minSampleSize: 5,
            });
            expect(suggestions.length).toBeGreaterThan(0);
            expect(suggestions[0].fpRate).toBe(1.0);
            expect(suggestions[0].suggestion).toBe("disable");
            expect(suggestions[0].commonReasons).toContain("test-code");
        });
        it("returns empty for low FP rate rules", async () => {
            // Submit mostly TPs
            for (let i = 0; i < 8; i++) {
                const finding = createFinding({ file: `src/file${i}.ts`, line: i });
                await submitFeedback(testDir, finding, "tp");
            }
            // And 2 FPs
            for (let i = 0; i < 2; i++) {
                const finding = createFinding({ file: `src/fp${i}.ts`, line: i });
                await submitFeedback(testDir, finding, "fp", { reason: "test-code" });
            }
            const suggestions = await getSuppressionSuggestions(testDir, {
                minFPRate: 0.5,
                minSampleSize: 5,
            });
            expect(suggestions).toHaveLength(0);
        });
    });
    describe("generateFeedbackReport", () => {
        it("generates comprehensive report", async () => {
            // Submit varied feedback
            const finding1 = createFinding({ scanner: "semgrep" });
            const finding2 = createFinding({ scanner: "eslint" });
            await submitFeedback(testDir, finding1, "fp", { reason: "test-code" });
            await submitFeedback(testDir, finding2, "fp", { reason: "false-pattern-match" });
            await submitFeedback(testDir, finding1, "tp");
            const report = await generateFeedbackReport(testDir);
            expect(report.overview.totalFeedback).toBe(3);
            expect(report.overview.tpCount).toBe(1);
            expect(report.overview.fpCount).toBe(2);
            expect(report.byScanner.length).toBeGreaterThan(0);
            expect(report.topFPReasons.length).toBeGreaterThan(0);
            expect(report.recentFeedback.length).toBe(3);
        });
        it("calculates FP rate correctly", async () => {
            const finding = createFinding();
            // 1 TP, 3 FP = 75% FP rate
            await submitFeedback(testDir, finding, "tp");
            await submitFeedback(testDir, finding, "fp", { reason: "test-code" });
            await submitFeedback(testDir, finding, "fp", { reason: "test-code" });
            await submitFeedback(testDir, finding, "fp", { reason: "test-code" });
            const report = await generateFeedbackReport(testDir);
            expect(report.overview.overallFPRate).toBe(0.75);
        });
    });
});
//# sourceMappingURL=fp-feedback.test.js.map