"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.securityChartsService = exports.SecurityChartsService = void 0;
const date_fns_1 = require("date-fns");
const drizzle_orm_1 = require("drizzle-orm");
const lodash_1 = __importDefault(require("lodash"));
const constants_1 = require("../../constants");
const database_1 = require("../../database");
const tables_1 = require("../../database/tables");
const metadata_1 = require("../../redteam/constants/metadata");
const workspaceUtils_1 = require("../controller/workspace/workspaceUtils");
const CATEGORY_COLORS = metadata_1.categoryColors;
class SecurityChartsService {
    constructor() { }
    static getInstance() {
        if (SecurityChartsService.instance === null) {
            SecurityChartsService.instance = new SecurityChartsService();
        }
        return SecurityChartsService.instance;
    }
    /**
     * Fetch security eval result rows from DB (same pattern as getTestRunMetricsGraph / getTopMetricFailures).
     * Returns eval results for completed security test runs in the given project/application/date range.
     */
    async getSecurityEvalResultsFromDb(params, options) {
        if (lodash_1.default.isEmpty(params.applicationId)) {
            throw new Error('Application ID is required');
        }
        const projectIds = (params.projectIds?.length ? params.projectIds : null) ??
            (params.projectId ? [params.projectId] : null);
        if (!projectIds?.length) {
            throw new Error('Project ID(s) are required');
        }
        const db = await (0, database_1.getDb)();
        const dateStart = (0, workspaceUtils_1.parseStartDate)(params.start, 7);
        const dateEnd = (0, workspaceUtils_1.parseEndDate)(params.end);
        const dateConditions = [
            (0, drizzle_orm_1.gte)(tables_1.testRunsTable.createdAt, dateStart),
            (0, drizzle_orm_1.lte)(tables_1.testRunsTable.createdAt, dateEnd),
        ];
        const applicationConditions = [
            (0, drizzle_orm_1.sql) `${tables_1.testRunsTable.applicationIds} @> ${JSON.stringify([params.applicationId])}::jsonb`,
        ];
        const testRunsWhereConditions = [
            (0, drizzle_orm_1.inArray)(tables_1.testRunsTable.projectId, projectIds),
            (0, drizzle_orm_1.eq)(tables_1.testRunsTable.status, constants_1.TEST_RUN_STATUS.COMPLETED),
            (0, drizzle_orm_1.eq)(tables_1.testRunsTable.type, constants_1.TEST_RUN_TYPE.SECURITY),
            ...dateConditions,
            ...applicationConditions,
        ];
        const testRuns = await db
            .select({ id: tables_1.testRunsTable.id })
            .from(tables_1.testRunsTable)
            .where((0, drizzle_orm_1.and)(...testRunsWhereConditions));
        if (testRuns.length === 0) {
            return [];
        }
        const testRunIds = testRuns.map((tr) => tr.id);
        const testRunToEvals = await db
            .select({
            testRunId: tables_1.testRunsToEvalsTable.testRunId,
            evalId: tables_1.testRunsToEvalsTable.evalId,
        })
            .from(tables_1.testRunsToEvalsTable)
            .where((0, drizzle_orm_1.inArray)(tables_1.testRunsToEvalsTable.testRunId, testRunIds));
        if (testRunToEvals.length === 0) {
            return [];
        }
        const allEvalIds = Array.from(new Set(testRunToEvals.map((rel) => rel.evalId)));
        const limit = Math.min(options?.limit ?? 10000, 10000);
        if (options?.groupByEvalId ?? false) {
            const rows = await db
                .select({
                id: tables_1.evalResultsTable.id,
                evalId: tables_1.evalResultsTable.evalId,
                createdAt: tables_1.evalResultsTable.createdAt,
                success: tables_1.evalResultsTable.success,
                error: tables_1.evalResultsTable.error,
                gradingResult: tables_1.evalResultsTable.gradingResult,
                traceId: tables_1.evalResultsTable.traceId,
            })
                .from(tables_1.evalResultsTable)
                .where((0, drizzle_orm_1.inArray)(tables_1.evalResultsTable.evalId, allEvalIds))
                .orderBy(options?.orderByCreatedDesc
                ? (0, drizzle_orm_1.desc)(tables_1.evalResultsTable.createdAt)
                : (0, drizzle_orm_1.asc)(tables_1.evalResultsTable.createdAt))
                .groupBy(tables_1.evalResultsTable.evalId)
                .limit(limit);
            return rows;
        }
        const rows = await db
            .select({
            id: tables_1.evalResultsTable.id,
            evalId: tables_1.evalResultsTable.evalId,
            createdAt: tables_1.evalResultsTable.createdAt,
            success: tables_1.evalResultsTable.success,
            error: tables_1.evalResultsTable.error,
            gradingResult: tables_1.evalResultsTable.gradingResult,
            traceId: tables_1.evalResultsTable.traceId,
        })
            .from(tables_1.evalResultsTable)
            .where((0, drizzle_orm_1.inArray)(tables_1.evalResultsTable.evalId, allEvalIds))
            .orderBy(options?.orderByCreatedDesc
            ? (0, drizzle_orm_1.desc)(tables_1.evalResultsTable.createdAt)
            : (0, drizzle_orm_1.asc)(tables_1.evalResultsTable.createdAt))
            .limit(limit);
        return rows;
    }
    /**
     * Security board chart: metric card – total security eval count and aggregated
     * total/pass/fail/error. totalSecurityEval from getWorkspaceDetails-style query;
     * total/pass/fail/error from getTestRunMetricsGraph-style aggregation (type security).
     */
    async getSecurityProbes(params) {
        if (lodash_1.default.isEmpty(params.applicationId)) {
            throw new Error('Application ID is required');
        }
        const projectIds = (params.projectIds?.length ? params.projectIds : null) ??
            (params.projectId ? [params.projectId] : null);
        if (!projectIds?.length) {
            throw new Error('Project ID(s) are required for security probes');
        }
        const db = await (0, database_1.getDb)();
        const dateStart = (0, workspaceUtils_1.parseStartDate)(params.start, 7);
        const dateEnd = (0, workspaceUtils_1.parseEndDate)(params.end);
        const dateConditions = [
            (0, drizzle_orm_1.gte)(tables_1.testRunsTable.createdAt, dateStart),
            (0, drizzle_orm_1.lte)(tables_1.testRunsTable.createdAt, dateEnd),
        ];
        const applicationConditions = [
            (0, drizzle_orm_1.sql) `${tables_1.testRunsTable.applicationIds} @> ${JSON.stringify([params.applicationId])}::jsonb`,
        ];
        // totalSecurityEval: same as getWorkspaceDetails security count
        const securityCountWhereConditions = [
            (0, drizzle_orm_1.inArray)(tables_1.testRunsTable.projectId, projectIds),
            (0, drizzle_orm_1.eq)(tables_1.testRunsTable.status, constants_1.TEST_RUN_STATUS.COMPLETED),
            (0, drizzle_orm_1.eq)(tables_1.testRunsTable.type, constants_1.TEST_RUN_TYPE.SECURITY),
            ...dateConditions,
            ...applicationConditions,
        ];
        const securityCountResult = await db
            .select({ count: (0, drizzle_orm_1.sql) `count(*)` })
            .from(tables_1.testRunsTable)
            .where((0, drizzle_orm_1.and)(...securityCountWhereConditions));
        const totalSecurityEval = Number(securityCountResult[0]?.count ?? 0);
        let total = 0;
        let pass = 0;
        let fail = 0;
        let errorCount = 0;
        const evalResults = await db
            .select({
            evalId: tables_1.evalResultsTable.evalId,
            gradingResult: tables_1.evalResultsTable.gradingResult,
            failureReason: tables_1.evalResultsTable.failureReason,
            success: tables_1.evalResultsTable.success,
        })
            .from(tables_1.evalResultsTable)
            .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(tables_1.evalResultsTable.applicationId, params.applicationId), (0, drizzle_orm_1.eq)(tables_1.evalResultsTable.isRedteam, 1), (0, drizzle_orm_1.gte)(tables_1.evalResultsTable.createdAt, dateStart), (0, drizzle_orm_1.lte)(tables_1.evalResultsTable.createdAt, dateEnd)));
        for (const result of evalResults) {
            total += 1;
            if (result?.success === 0 && result.failureReason === 2) {
                errorCount += 1;
            }
            else if (result?.success === 0 && result.failureReason === 1) {
                fail += 1;
            }
            else {
                pass += 1;
            }
        }
        return {
            totalSecurityEval,
            total,
            pass,
            fail,
            error: errorCount,
        };
    }
    /** Security board chart: metric card – vulnerabilities (failures) count */
    async getVulnerabilitiesDetected(params) {
        const rows = await this.getSecurityEvalResultsFromDb(params);
        let total = 0;
        let failures = 0;
        for (const row of rows) {
            total += 1;
            const grading = row?.gradingResult ?? row?.grading_result;
            if (row?.error != null && row.error !== '') {
                // errors not counted as failures
            }
            else if (grading?.pass) {
                // pass
            }
            else {
                failures += 1;
            }
        }
        return { failures, total };
    }
    /** Security board chart: bar chart – vulnerabilities over time */
    async getVulnerabilitiesOverTime(params) {
        if (lodash_1.default.isEmpty(params.applicationId) ||
            lodash_1.default.isEmpty(params.start) ||
            lodash_1.default.isEmpty(params.end)) {
            throw new Error('Application ID, start, and end are required');
        }
        const rows = await this.getSecurityEvalResultsFromDb(params);
        const useWeekly = params.type === '3M' || params.type === '6M';
        const startDate = (0, date_fns_1.parseISO)(params.start);
        const endDate = (0, date_fns_1.parseISO)(params.end);
        let barData;
        if (useWeekly) {
            const weekCounts = {};
            rows.forEach((run) => {
                const grading = run?.gradingResult ?? run?.grading_result;
                const componentResults = grading?.componentResults ?? [];
                const createdAt = run?.createdAt;
                if (!createdAt)
                    return;
                const created = typeof createdAt === 'string' ? (0, date_fns_1.parseISO)(createdAt) : createdAt;
                const weekStartDate = (0, date_fns_1.startOfWeek)(created, { weekStartsOn: 1 });
                const weekEndDate = (0, date_fns_1.endOfWeek)(created, { weekStartsOn: 1 });
                const weekKey = (0, date_fns_1.format)(weekStartDate, 'yyyy-MM-dd');
                if (!weekCounts[weekKey]) {
                    weekCounts[weekKey] = {
                        count: 0,
                        weekStart: weekStartDate,
                        weekEnd: weekEndDate,
                    };
                }
                componentResults.forEach((item) => {
                    if (item.pass === false)
                        weekCounts[weekKey].count += 1;
                });
            });
            const weekKeys = [];
            let current = (0, date_fns_1.startOfWeek)(startDate, { weekStartsOn: 1 });
            const last = (0, date_fns_1.endOfWeek)(endDate, { weekStartsOn: 1 });
            while (current <= last) {
                weekKeys.push((0, date_fns_1.format)(current, 'yyyy-MM-dd'));
                current = new Date(current.getTime() + 7 * 24 * 60 * 60 * 1000);
            }
            barData = weekKeys.map((weekKey) => {
                const week = weekCounts[weekKey];
                return {
                    x: weekKey,
                    y: week ? week.count : 0,
                    weekStart: week ? week.weekStart : (0, date_fns_1.parseISO)(weekKey),
                    weekEnd: week
                        ? week.weekEnd
                        : (0, date_fns_1.endOfWeek)((0, date_fns_1.parseISO)(weekKey), { weekStartsOn: 1 }),
                };
            });
        }
        else {
            const days = (0, date_fns_1.eachDayOfInterval)({ start: startDate, end: endDate });
            const dayCounts = {};
            rows.forEach((run) => {
                const grading = run?.gradingResult ?? run?.grading_result;
                const componentResults = grading?.componentResults ?? [];
                const createdAt = run?.createdAt;
                if (!createdAt)
                    return;
                const day = typeof createdAt === 'string'
                    ? (0, date_fns_1.format)((0, date_fns_1.parseISO)(createdAt), 'yyyy-MM-dd')
                    : (0, date_fns_1.format)(createdAt, 'yyyy-MM-dd');
                componentResults.forEach((item) => {
                    if (item.pass === false) {
                        dayCounts[day] = (dayCounts[day] || 0) + 1;
                    }
                });
            });
            barData = days.map((date) => ({
                x: (0, date_fns_1.format)(date, 'yyyy-MM-dd'),
                y: dayCounts[(0, date_fns_1.format)(date, 'yyyy-MM-dd')] || 0,
                date,
            }));
        }
        return barData;
    }
    /** Security board chart: polar/radar – category-wise volume */
    async getCategoryWiseVolumeAnalysis(params) {
        const rows = await this.getSecurityEvalResultsFromDb(params);
        const categoryVulnMap = {};
        rows.forEach((run) => {
            const grading = run?.gradingResult ?? run?.grading_result;
            const componentResults = grading?.componentResults ?? [];
            componentResults.forEach((item) => {
                const pluginId = item?.metadata?.pluginId;
                if (!pluginId)
                    return;
                Object.entries(metadata_1.riskCategories).forEach(([category, plugins]) => {
                    if (plugins.includes(pluginId) && item.pass === false) {
                        categoryVulnMap[category] = (categoryVulnMap[category] || 0) + 1;
                    }
                });
            });
        });
        return Object.entries(categoryVulnMap)
            .filter(([, value]) => value > 0)
            .map(([category, value]) => ({
            label: category,
            value,
            color: CATEGORY_COLORS[category] ?? '#999',
        }));
    }
    /**
     * Security board: recent security evaluations as graph data (count per day for line chart).
     * Single query: eval_results joined to test_runs via test_runs_to_evals; only createdAt selected.
     */
    async getRecentSecurityEvaluations(params) {
        if (lodash_1.default.isEmpty(params.applicationId)) {
            throw new Error('Application ID is required');
        }
        const projectIds = (params.projectIds?.length ? params.projectIds : null) ??
            (params.projectId ? [params.projectId] : null);
        if (!projectIds?.length) {
            throw new Error('Project ID(s) are required');
        }
        if (lodash_1.default.isEmpty(params.start) || lodash_1.default.isEmpty(params.end)) {
            throw new Error('Start and end dates are required');
        }
        const startStr = params.start;
        const endStr = params.end;
        const db = await (0, database_1.getDb)();
        const dateStart = (0, workspaceUtils_1.parseStartDate)(startStr, 7);
        const dateEnd = (0, workspaceUtils_1.parseEndDate)(endStr);
        const rows = await db
            .select({
            createdAt: tables_1.testRunsTable.createdAt,
        })
            .from(tables_1.testRunsTable)
            .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.inArray)(tables_1.testRunsTable.projectId, projectIds), (0, drizzle_orm_1.eq)(tables_1.testRunsTable.status, constants_1.TEST_RUN_STATUS.COMPLETED), (0, drizzle_orm_1.eq)(tables_1.testRunsTable.type, constants_1.TEST_RUN_TYPE.SECURITY), (0, drizzle_orm_1.gte)(tables_1.testRunsTable.createdAt, dateStart), (0, drizzle_orm_1.lte)(tables_1.testRunsTable.createdAt, dateEnd), (0, drizzle_orm_1.sql) `${tables_1.testRunsTable.applicationIds} @> ${JSON.stringify([params.applicationId])}::jsonb`));
        const startDate = (0, date_fns_1.parseISO)(startStr);
        const endDate = (0, date_fns_1.parseISO)(endStr);
        const days = (0, date_fns_1.eachDayOfInterval)({ start: startDate, end: endDate });
        const countByDay = {};
        days.forEach((d) => {
            countByDay[(0, date_fns_1.format)(d, 'yyyy-MM-dd')] = 0;
        });
        rows.forEach((row) => {
            const createdAt = row?.createdAt;
            if (!createdAt)
                return;
            const day = typeof createdAt === 'string'
                ? (0, date_fns_1.format)((0, date_fns_1.parseISO)(createdAt), 'yyyy-MM-dd')
                : (0, date_fns_1.format)(createdAt, 'yyyy-MM-dd');
            if (countByDay[day] !== undefined)
                countByDay[day] += 1;
        });
        return days.map((d) => ({
            timestamp: (0, date_fns_1.format)(d, 'yyyy-MM-dd'),
            count: countByDay[(0, date_fns_1.format)(d, 'yyyy-MM-dd')] ?? 0,
        }));
    }
    /** Security board: recent security evaluation runs (table) */
    async getRecentSecurityEvaluationsList(params) {
        if (lodash_1.default.isEmpty(params.applicationId)) {
            throw new Error('Application ID is required');
        }
        const startStr = params.start;
        const endStr = params.end;
        const dateStart = (0, workspaceUtils_1.parseStartDate)(startStr, 7);
        const dateEnd = (0, workspaceUtils_1.parseEndDate)(endStr);
        const db = await (0, database_1.getDb)();
        const limit = Math.min(params.limit ?? 10, 100);
        const results = await db
            .select({
            id: tables_1.testRunsTable.id,
            type: tables_1.testRunsTable.type,
            status: tables_1.testRunsTable.status,
            createdAt: tables_1.testRunsTable.createdAt,
            projectId: tables_1.testRunsTable.projectId,
            applicationIds: tables_1.testRunsTable.applicationIds,
            libraryId: tables_1.testRunsTable.libraryId,
            evalId: tables_1.testRunsToEvalsTable.evalId,
            redteamConfig: tables_1.evalsTable.config,
            // Add security config fields
            securityConfigId: tables_1.testRunsToSecurityConfigsTable.securityConfigId,
            securityConfigName: tables_1.securityConfigTable?.name,
            securityConfig: tables_1.securityConfigTable.config,
            securityConfigRunSnapshot: tables_1.testRunsToSecurityConfigsTable.config,
        })
            .from(tables_1.testRunsTable)
            .leftJoin(tables_1.testRunsToEvalsTable, (0, drizzle_orm_1.sql) `${tables_1.testRunsTable.id} = ${tables_1.testRunsToEvalsTable.testRunId}`)
            .leftJoin(tables_1.evalsTable, (0, drizzle_orm_1.sql) `${tables_1.testRunsToEvalsTable.evalId} = ${tables_1.evalsTable.id}`)
            .leftJoin(tables_1.testRunsToSecurityConfigsTable, (0, drizzle_orm_1.sql) `${tables_1.testRunsTable.id} = ${tables_1.testRunsToSecurityConfigsTable.testRunId}`)
            .leftJoin(tables_1.securityConfigTable, (0, drizzle_orm_1.sql) `${tables_1.testRunsToSecurityConfigsTable.securityConfigId} = ${tables_1.securityConfigTable.id}`)
            .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.inArray)(tables_1.testRunsTable.applicationIds, [[params.applicationId]]), (0, drizzle_orm_1.eq)(tables_1.testRunsTable.type, constants_1.TEST_RUN_TYPE.SECURITY), (0, drizzle_orm_1.gte)(tables_1.testRunsTable.createdAt, dateStart), (0, drizzle_orm_1.lte)(tables_1.testRunsTable.createdAt, dateEnd)))
            .limit(limit)
            .orderBy((0, drizzle_orm_1.desc)(tables_1.testRunsTable.createdAt));
        const [application] = await db
            .select({
            id: tables_1.applicationsTable.id,
            name: tables_1.applicationsTable.name,
        })
            .from(tables_1.applicationsTable)
            .where((0, drizzle_orm_1.eq)(tables_1.applicationsTable.id, params.applicationId));
        const processedResults = results.map((result) => {
            const { securityConfigRunSnapshot, ...rest } = result;
            const securityConfig = securityConfigRunSnapshot ??
                rest.securityConfig;
            return {
                ...rest,
                securityConfig,
                applicationName: application?.name,
                title: `Security Eval Run of ${application?.name} using ${rest?.securityConfigName}`,
            };
        });
        return processedResults;
    }
    /**
     * Security board: probe results over time.
     * Same response format as getTestRunMetricsGraph: { graphData } with graphData from getDateRangeData.
     * Logic aligned with getTestRunMetricsGraph (type: 'security').
     */
    async getProbeResultsOverTime(params) {
        if (lodash_1.default.isEmpty(params.applicationId) ||
            lodash_1.default.isEmpty(params.start) ||
            lodash_1.default.isEmpty(params.end)) {
            throw new Error('Application ID, start, and end are required');
        }
        const dateStart = (0, workspaceUtils_1.parseStartDate)(params.start, 7);
        const dateEnd = (0, workspaceUtils_1.parseEndDate)(params.end);
        const db = await (0, database_1.getDb)();
        const evalResults = await db
            .select({
            evalId: tables_1.evalResultsTable.evalId,
            gradingResult: tables_1.evalResultsTable.gradingResult,
            failureReason: tables_1.evalResultsTable.failureReason,
            success: tables_1.evalResultsTable.success,
            createdAt: tables_1.evalResultsTable.createdAt,
        })
            .from(tables_1.evalResultsTable)
            .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(tables_1.evalResultsTable.applicationId, params.applicationId), (0, drizzle_orm_1.eq)(tables_1.evalResultsTable.isRedteam, 1), (0, drizzle_orm_1.gte)(tables_1.evalResultsTable.createdAt, dateStart), (0, drizzle_orm_1.lte)(tables_1.evalResultsTable.createdAt, dateEnd)));
        const graphData = (0, workspaceUtils_1.getDateRangeData)(dateStart, dateEnd);
        const graphDataMap = new Map();
        for (const dayData of graphData) {
            graphDataMap.set(dayData.date, dayData);
        }
        for (const result of evalResults) {
            let day = null;
            if (result.createdAt) {
                const d = new Date(result.createdAt);
                const year = d.getUTCFullYear();
                const month = String(d.getUTCMonth() + 1).padStart(2, '0');
                const dayNum = String(d.getUTCDate()).padStart(2, '0');
                day = `${year}-${month}-${dayNum}`;
            }
            const dayData = day ? graphDataMap.get(day) : null;
            if (!dayData) {
                continue;
            }
            dayData.total += 1;
            if (result?.success === 0 && result.failureReason === 2) {
                dayData.error += 1;
            }
            else if (result?.success === 0 && result.failureReason === 1) {
                dayData.fail += 1;
            }
            else {
                dayData.pass += 1;
            }
        }
        return { graphData };
    }
    /** Security board: top security control failures (same logic as getTopMetricFailures with type 'security') */
    async getTopSecurityControlFailures(params) {
        const rows = await this.getSecurityEvalResultsFromDb(params);
        const metricStats = new Map();
        for (const run of rows) {
            const grading = run?.gradingResult ?? run?.grading_result;
            const componentResults = grading?.componentResults ?? [];
            for (const componentResult of componentResults) {
                const assertion = componentResult?.assertion;
                if (assertion?.type === 'human') {
                    continue;
                }
                const metricName = assertion?.displayLabel ||
                    assertion?.type ||
                    'Unknown';
                if (!metricStats.has(metricName)) {
                    metricStats.set(metricName, {
                        total: 0,
                        failed: 0,
                        metricName,
                    });
                }
                const stats = metricStats.get(metricName);
                stats.total += 1;
                if (!componentResult?.pass) {
                    stats.failed += 1;
                }
            }
        }
        const limit = Math.min(params.limit ?? 50, 100);
        return Array.from(metricStats.values())
            .map((stats) => ({
            control: stats.metricName,
            failureCount: stats.failed,
            total: stats.total,
            passCount: stats.total - stats.failed,
            failureRate: stats.total > 0 ? (stats.failed / stats.total) * 100 : 0,
        }))
            .filter((row) => row.failureCount > 0)
            .sort((a, b) => {
            if (b.failureCount !== a.failureCount) {
                return b.failureCount - a.failureCount;
            }
            return b.failureRate - a.failureRate;
        })
            .slice(0, limit);
    }
}
exports.SecurityChartsService = SecurityChartsService;
SecurityChartsService.instance = null;
exports.securityChartsService = SecurityChartsService.getInstance();
//# sourceMappingURL=securityChartsService.js.map