/**
 * Findings Repository
 *
 * CRUD operations for security findings with status tracking.
 *
 * @module persistence/repositories/findings
 */
import { randomUUID } from "crypto";
export class FindingsRepository {
    db;
    constructor(db) {
        this.db = db;
    }
    create(finding) {
        const id = randomUUID();
        const now = new Date().toISOString();
        this.db
            .prepare(`INSERT INTO findings (
          id, project_id, certification_id, severity, category, file, line, column_num,
          description, scanner_source, rule_id, status, confidence, cwe_ids,
          first_seen_at, last_seen_at, metadata
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
            .run(id, finding.projectId, finding.certificationId || null, finding.severity, finding.category, finding.file, finding.line, finding.column || null, finding.description, finding.scannerSource, finding.ruleId || null, finding.status || "open", finding.confidence || 100, finding.cweIds ? JSON.stringify(finding.cweIds) : null, now, now, finding.metadata ? JSON.stringify(finding.metadata) : null);
        return {
            ...finding,
            id,
            firstSeenAt: now,
            lastSeenAt: now,
            status: finding.status || "open",
        };
    }
    upsertFromScan(finding) {
        const existing = this.findBySignature(finding.projectId, finding.file, finding.line, finding.category, finding.scannerSource);
        if (existing) {
            this.db
                .prepare("UPDATE findings SET last_seen_at = datetime('now') WHERE id = ?")
                .run(existing.id);
            return {
                finding: { ...existing, lastSeenAt: new Date().toISOString() },
                isNew: false,
            };
        }
        const created = this.create({ ...finding, status: "open" });
        return { finding: created, isNew: true };
    }
    findById(id) {
        const row = this.db
            .prepare("SELECT * FROM findings WHERE id = ?")
            .get(id);
        return row ? this.mapRow(row) : undefined;
    }
    findBySignature(projectId, file, line, category, scannerSource) {
        const row = this.db
            .prepare(`SELECT * FROM findings
         WHERE project_id = ? AND file = ? AND line = ? AND category = ? AND scanner_source = ?
         AND status != 'fixed'`)
            .get(projectId, file, line, category, scannerSource);
        return row ? this.mapRow(row) : undefined;
    }
    find(filter) {
        let sql = "SELECT * FROM findings WHERE 1=1";
        const params = [];
        if (filter.projectId) {
            sql += " AND project_id = ?";
            params.push(filter.projectId);
        }
        if (filter.certificationId) {
            sql += " AND certification_id = ?";
            params.push(filter.certificationId);
        }
        if (filter.severity) {
            const severities = Array.isArray(filter.severity) ? filter.severity : [filter.severity];
            sql += ` AND severity IN (${severities.map(() => "?").join(", ")})`;
            params.push(...severities);
        }
        if (filter.status) {
            const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];
            sql += ` AND status IN (${statuses.map(() => "?").join(", ")})`;
            params.push(...statuses);
        }
        if (filter.category) {
            const categories = Array.isArray(filter.category) ? filter.category : [filter.category];
            sql += ` AND category IN (${categories.map(() => "?").join(", ")})`;
            params.push(...categories);
        }
        if (filter.file) {
            sql += " AND file LIKE ?";
            params.push(`%${filter.file}%`);
        }
        if (filter.scannerSource) {
            sql += " AND scanner_source = ?";
            params.push(filter.scannerSource);
        }
        if (filter.sinceDate) {
            sql += " AND first_seen_at >= ?";
            params.push(filter.sinceDate);
        }
        if (filter.untilDate) {
            sql += " AND first_seen_at <= ?";
            params.push(filter.untilDate);
        }
        sql += " ORDER BY first_seen_at DESC";
        if (filter.limit) {
            sql += " LIMIT ?";
            params.push(filter.limit);
        }
        if (filter.offset) {
            sql += " OFFSET ?";
            params.push(filter.offset);
        }
        const rows = this.db.prepare(sql).all(...params);
        return rows.map((r) => this.mapRow(r));
    }
    updateStatus(id, status, fixedBy) {
        const updates = ["status = ?"];
        const params = [status];
        if (status === "fixed") {
            updates.push("fixed_at = datetime('now')");
            if (fixedBy) {
                updates.push("fixed_by = ?");
                params.push(fixedBy);
            }
        }
        params.push(id);
        const result = this.db
            .prepare(`UPDATE findings SET ${updates.join(", ")} WHERE id = ?`)
            .run(...params);
        if (result.changes > 0) {
            this.db
                .prepare(`INSERT INTO fix_history (finding_id, action, performed_by)
           VALUES (?, ?, ?)`)
                .run(id, status === "fixed" ? "fixed" : status, fixedBy || null);
        }
        return result.changes > 0;
    }
    markFixed(ids, fixedBy) {
        let count = 0;
        this.db.transaction(() => {
            for (const id of ids) {
                if (this.updateStatus(id, "fixed", fixedBy)) {
                    count++;
                }
            }
        })();
        return count;
    }
    markFalsePositive(id, reason) {
        const result = this.db
            .prepare(`UPDATE findings
         SET status = 'false_positive', false_positive = 1, false_positive_reason = ?
         WHERE id = ?`)
            .run(reason || null, id);
        if (result.changes > 0) {
            this.db
                .prepare(`INSERT INTO fix_history (finding_id, action, notes)
           VALUES (?, 'marked_false_positive', ?)`)
                .run(id, reason || null);
        }
        return result.changes > 0;
    }
    getStats(projectId) {
        const countsBySeverity = this.db
            .prepare(`SELECT severity, COUNT(*) as count
         FROM findings WHERE project_id = ? AND status = 'open'
         GROUP BY severity`)
            .all(projectId);
        const countsByCategory = this.db
            .prepare(`SELECT category, COUNT(*) as count
         FROM findings WHERE project_id = ? AND status = 'open'
         GROUP BY category`)
            .all(projectId);
        const countsByScanner = this.db
            .prepare(`SELECT scanner_source, COUNT(*) as count
         FROM findings WHERE project_id = ?
         GROUP BY scanner_source`)
            .all(projectId);
        const totals = this.db
            .prepare(`SELECT
           COUNT(*) as total,
           SUM(CASE WHEN status = 'open' THEN 1 ELSE 0 END) as open,
           SUM(CASE WHEN status = 'fixed' THEN 1 ELSE 0 END) as fixed,
           SUM(CASE WHEN status = 'false_positive' THEN 1 ELSE 0 END) as false_positives
         FROM findings WHERE project_id = ?`)
            .get(projectId);
        const avgMttr = this.db
            .prepare(`SELECT AVG((julianday(fixed_at) - julianday(first_seen_at)) * 24) as avg_hours
         FROM findings WHERE project_id = ? AND status = 'fixed' AND fixed_at IS NOT NULL`)
            .get(projectId);
        const fixVelocity = this.db
            .prepare(`SELECT COUNT(*) * 1.0 / MAX(1, (julianday('now') - julianday(MIN(fixed_at))) / 7) as velocity
         FROM findings WHERE project_id = ? AND status = 'fixed'`)
            .get(projectId);
        const scanDates = this.db
            .prepare(`SELECT MIN(started_at) as first_scan, MAX(started_at) as last_scan
         FROM scans WHERE project_id = ?`)
            .get(projectId);
        return {
            projectId,
            totalFindings: totals?.total || 0,
            openFindings: totals?.open || 0,
            fixedFindings: totals?.fixed || 0,
            falsePositives: totals?.false_positives || 0,
            bySeverity: Object.fromEntries(countsBySeverity.map((r) => [r.severity, r.count])),
            byCategory: Object.fromEntries(countsByCategory.map((r) => [r.category, r.count])),
            byScanner: Object.fromEntries(countsByScanner.map((r) => [r.scanner_source, r.count])),
            avgMttrHours: avgMttr?.avg_hours || undefined,
            fixVelocityPerWeek: fixVelocity?.velocity || undefined,
            firstScanAt: scanDates?.first_scan || undefined,
            lastScanAt: scanDates?.last_scan || undefined,
        };
    }
    mapRow(row) {
        return {
            id: row.id,
            projectId: row.project_id,
            certificationId: row.certification_id || undefined,
            severity: row.severity,
            category: row.category,
            file: row.file,
            line: row.line,
            column: row.column_num || undefined,
            description: row.description,
            scannerSource: row.scanner_source,
            ruleId: row.rule_id || undefined,
            status: row.status,
            confidence: row.confidence,
            cweIds: row.cwe_ids ? JSON.parse(row.cwe_ids) : undefined,
            firstSeenAt: row.first_seen_at,
            lastSeenAt: row.last_seen_at,
            fixedAt: row.fixed_at || undefined,
            fixedBy: row.fixed_by || undefined,
            falsePositive: row.false_positive === 1,
            falsePositiveReason: row.false_positive_reason || undefined,
            metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
        };
    }
}
//# sourceMappingURL=findings.js.map