"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GuardRailsService = void 0;
const tables_1 = require("../../database/tables");
const database_1 = require("../../database");
const logger_1 = require("../../logger");
const drizzle_orm_1 = require("drizzle-orm");
const clickhouseService_1 = require("./clickhouseService");
const lodash_1 = __importDefault(require("lodash"));
const sendBillingLogs_1 = require("../../util/sendBillingLogs");
const bson_1 = require("bson");
const decimal_js_1 = __importDefault(require("decimal.js"));
const billing_logger_1 = __importDefault(require("./billing/billing-logger"));
const get_upgrade_meta_1 = require("../../util/get-upgrade-meta");
const callControlPlaneApi_1 = require("../../util/callContolPlane/callControlPlaneApi");
const restriction_message_1 = require("../constants/restriction.message");
class GuardRailsService {
    // Helper functions
    static dayKey(d) {
        return d.toISOString().slice(0, 10);
    }
    static monthKey(d) {
        return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, '0')}`;
    }
    static percentile(values, p) {
        if (!values.length) {
            return 0;
        }
        const sorted = [...values].sort((a, b) => a - b);
        const idx = Math.min(sorted.length - 1, Math.floor((p / 100) * sorted.length));
        return sorted[idx];
    }
    /**
     * Common function to check guardrails restrictions and send notifications
     * @param params - Parameters for restriction check
     * @returns true if limit reached and should block, false otherwise
     */
    static async checkGuardrailsRestrictions(params) {
        const { workspaceId, isEnterprise, isStarter, isProfessional, usages, restrictionsData, notifications, exhaustedNotificationMap, } = params;
        // Check if guardrails limit is reached
        if (!isEnterprise
            && restrictionsData?.guardrails > 0
            && new decimal_js_1.default(usages?.guardrails ?? 0).gte(restrictionsData?.guardrails ?? 0)) {
            // For starter and professional plans, send limit reached notification
            if (isStarter || isProfessional) {
                const hasSentNotification = exhaustedNotificationMap?.get(workspaceId)?.wallet; // Use wallet field as guardrails isn't in the map type
                if (!hasSentNotification
                    && notifications?.canSendGuardrailsUsageExceeded) {
                    await (0, callControlPlaneApi_1.callControlPlane)(`/trusys-emails/limit-reached`, {
                        method: 'POST',
                        params: {
                            orgId: workspaceId,
                            type: 'Guardrails',
                            orgType: isStarter ? 'Starter' : isProfessional ? 'Professional' : 'Enterprise',
                            currentUsage: usages?.guardrails ?? 0,
                        },
                    }, '');
                    // Mark as exhausted if map is provided
                    if (exhaustedNotificationMap) {
                        exhaustedNotificationMap.set(workspaceId, {
                            spans: exhaustedNotificationMap?.get(workspaceId)?.spans ?? false,
                            metric: exhaustedNotificationMap?.get(workspaceId)?.metric ?? false,
                            wallet: true, // Using wallet field as guardrails isn't in the map type
                        });
                    }
                }
            }
            // For starter plans, block the operation
            if (isStarter) {
                return true;
            }
        }
        return false;
    }
    /**
     * Ensure ClickHouse guardrail_results table exists.
     * Safe to call multiple times (uses CREATE TABLE IF NOT EXISTS).
     */
    static async ensureGuardrailResultsTable() {
        const createTableSql = `
      CREATE TABLE IF NOT EXISTS ${GuardRailsService.GUARDRAIL_RESULTS_TABLE}
      (
        id UUID,
        application_id String,
        project_id String,
        guardrail_stage String,
        prompt String,
        response String,
        latency Float64,
        pass Bool,
        score Float64,
        error String,
        execution_time Float64,
        component_results String,
        app_version String,
        environment String,
        metadata JSON,
        final_content String,
        created_at DateTime DEFAULT now()
      )
      ENGINE = MergeTree
      ORDER BY (project_id, application_id, created_at)
    `;
        await clickhouseService_1.clickHouseService.command(createTableSql);
    }
    /**
     * Persist an array of guardrail results into ClickHouse.
     */
    static async saveGuardrailResults(results, projectId, workspaceId) {
        if (!Array.isArray(results) || results.length === 0) {
            return;
        }
        await GuardRailsService.ensureGuardrailResultsTable();
        // Aggregate token usage from componentResults and send billing logs
        const totalTokenUsage = {
            prompt: new decimal_js_1.default(0),
            completion: new decimal_js_1.default(0),
            total: new decimal_js_1.default(0),
            cached: new decimal_js_1.default(0),
        };
        // Fetch upgrade metadata for bucket charges and restrictions
        let isEnterprise = false;
        let isStarter = false;
        let isProfessional = false;
        let usages = {};
        let restrictionsData = {};
        const exhaustedNotificationMap = new Map();
        // Check bucket charges and restrictions BEFORE saving
        try {
            const upgradeMeta = await (0, get_upgrade_meta_1.getUpgradeMetaByOrgId)(workspaceId, {
                fetchGuardrailsUsage: true,
            });
            isEnterprise = upgradeMeta.isEnterprise;
            isStarter = upgradeMeta.isStarter;
            isProfessional = !isEnterprise && !isStarter; // Professional plan is neither enterprise nor starter
            usages = upgradeMeta.usages;
            restrictionsData = upgradeMeta.restrictionsData;
            const { bucketInfo, notifications } = upgradeMeta;
            // Check guardrails restrictions using common function
            const shouldBlock = await GuardRailsService.checkGuardrailsRestrictions({
                workspaceId,
                isEnterprise,
                isStarter,
                isProfessional,
                usages,
                restrictionsData,
                notifications,
                exhaustedNotificationMap,
            });
            if (shouldBlock) {
                throw new Error(restriction_message_1.ERROR_RESTRICTION_MESSAGE.guardrails.title);
            }
            // Check bucket charges and calculate extra buckets needed for guardrails
            if (!isEnterprise
                && !isStarter
                && bucketInfo
                && bucketInfo.guardrails) {
                const guardrailBucketSize = bucketInfo.guardrails.bucketSize ?? 0;
                const guardrailBucketsCharged = bucketInfo.guardrails.bucketsCharged ?? 0;
                const guardrailFreeTierLimit = bucketInfo.guardrails.freeTierLimit ?? 0;
                const guardrailCurrentUsage = new decimal_js_1.default(usages?.guardrails ?? 0).plus(results.length).toNumber();
                // Calculate if extra buckets are needed based on current usage
                if (guardrailBucketSize > 0 && guardrailCurrentUsage > guardrailFreeTierLimit) {
                    const usageBeyondFree = new decimal_js_1.default(guardrailCurrentUsage).minus(guardrailFreeTierLimit);
                    const alreadyCoveredUsage = new decimal_js_1.default(guardrailBucketsCharged).mul(guardrailBucketSize);
                    const uncoveredUsage = usageBeyondFree.minus(alreadyCoveredUsage);
                    const extraBucketsNeeded = uncoveredUsage.gt(0)
                        ? uncoveredUsage.div(guardrailBucketSize).ceil().toNumber()
                        : 0;
                    if (extraBucketsNeeded > 0) {
                        // Call API to charge bucket - calculates cost, checks wallet, deducts, and updates tracking
                        try {
                            const response = await (0, callControlPlaneApi_1.callControlPlane)(`/trusys/billing-plans/charge-bucket`, {
                                method: 'POST',
                                params: {
                                    orgId: workspaceId,
                                    bucketType: 'guardrails',
                                    extraBucketsNeeded,
                                    currentUsage: guardrailCurrentUsage,
                                },
                            }, '');
                            if (response?.error) {
                                throw new Error(response.error || 'Failed to charge guardrail bucket');
                            }
                        }
                        catch (error) {
                            logger_1.logger.error(`Error charging guardrail bucket: ${error}`);
                            const errorMessage = error instanceof Error ? error.message : String(error);
                            throw new Error(errorMessage.includes('Insufficient')
                                ? errorMessage
                                : 'Failed to charge guardrail bucket. Please try again.');
                        }
                    }
                }
            }
        }
        catch (error) {
            logger_1.logger.error(`Error checking guardrail bucket charges: ${error}`);
            throw error; // Re-throw to prevent saving if bucket charge fails
        }
        const rows = results?.map((item) => {
            const componentResults = item?.componentResults ?? [];
            componentResults.forEach((component) => {
                // Check for tokenUsage or tokensUsed (support both naming conventions)
                const tokenUsage = component.tokenUsage || component.tokensUsed;
                if (tokenUsage && typeof tokenUsage === 'object') {
                    totalTokenUsage.prompt = totalTokenUsage.prompt.plus(tokenUsage.prompt || 0);
                    totalTokenUsage.completion = totalTokenUsage.completion.plus(tokenUsage.completion || 0);
                    totalTokenUsage.total = totalTokenUsage.total.plus(tokenUsage.total || 0);
                    totalTokenUsage.cached = totalTokenUsage.cached.plus(tokenUsage.cached || 0);
                }
            });
            return {
                id: crypto.randomUUID(),
                application_id: item.application_id,
                project_id: projectId,
                guardrail_stage: item.guardrail_stage,
                prompt: item.prompt ?? '',
                response: item.response ?? '',
                latency: item.latency ?? 0,
                pass: !!item.pass,
                score: item.score ?? 0,
                error: item.error ?? '',
                execution_time: item.execution_time ?? 0,
                final_content: item.final_content ?? '',
                component_results: JSON.stringify(item.componentResults ?? []),
                app_version: item.app_version ?? '',
                environment: item.environment ?? '',
                metadata: item.metadata ?? {},
            };
        });
        await clickhouseService_1.clickHouseService.insert(GuardRailsService.GUARDRAIL_RESULTS_TABLE, rows);
        logger_1.logger.info(`Inserted ${rows.length} guardrail result rows into ClickHouse`);
        // Send billing logs if there's any token usage
        // Log conditions:
        // 1. Always log if isEnterprise
        // 2. Only log if !isEnterprise && !isStarter && usage >= limit
        const shouldLogGuardrailsRequest = (totalTokenUsage.total.gt(0) || totalTokenUsage.prompt.gt(0) || totalTokenUsage.completion.gt(0) || totalTokenUsage.cached.gt(0)) &&
            (isEnterprise ||
                (!isEnterprise
                    && !isStarter
                    && restrictionsData?.guardrails > 0
                    && new decimal_js_1.default(usages?.guardrails ?? 0).gte(restrictionsData?.guardrails ?? 0)));
        if (shouldLogGuardrailsRequest) {
            try {
                const logEntry = {
                    id: new bson_1.ObjectId().toHexString(),
                    workspaceId,
                    projectId,
                    type: 'guardrailsRequest',
                    typeId: results[0].application_id,
                    provider: 'trusys',
                    model: 'gaurdrail-slm',
                    currency: 'TRUCREDS',
                    cost: 0, // Cost calculation can be added later if needed
                    tokenUsage: totalTokenUsage,
                    timestamp: new Date().toISOString(),
                    metadata: {
                        totalResults: results.length,
                        guardrailStages: [...new Set(results.map((r) => r.guardrail_stage))],
                        subType: 'guardrailsRequest',
                    },
                };
                await (0, sendBillingLogs_1.sendBillingLogsToApi)([logEntry]);
                logger_1.logger.info(`Sent billing log for guardrails: ${totalTokenUsage.total} total tokens across ${results.length} results`);
            }
            catch (error) {
                logger_1.logger.error(`Error sending billing logs for guardrails: ${error}`);
                // Continue execution even if billing log fails
            }
        }
        billing_logger_1.default.logGuardrailUsage({
            workspaceId,
            projectId,
            typeId: results[0].application_id,
            cost: 0,
            tokenUsage: {},
            amount: results.length,
            metadata: {
                applicationId: results[0].application_id,
                projectId,
                guardrailStages: [...new Set(results.map((r) => r.guardrail_stage))],
                subType: 'guardrails',
            },
        });
    }
    /**
     * Fetch guardrail results for an application/project within a time range.
     */
    static async fetchGuardrailResults(params) {
        const { applicationId, projectId, start, end } = params;
        await GuardRailsService.ensureGuardrailResultsTable();
        const sql = `
      SELECT
        application_id,
        project_id,
        guardrail_stage,
        prompt,
        response,
        latency,
        pass,
        score,
        error,
        execution_time,
        component_results,
        app_version,
        environment,
        metadata,
        final_content,
        created_at
      FROM ${GuardRailsService.GUARDRAIL_RESULTS_TABLE}
      WHERE application_id = {applicationId:String}
        AND project_id = {projectId:String}
        AND created_at BETWEEN parseDateTimeBestEffort({start:String}) AND parseDateTimeBestEffort({end:String})
      ORDER BY created_at DESC
    `;
        const rows = await clickhouseService_1.clickHouseService.query(sql, {
            applicationId,
            projectId,
            start,
            end,
        });
        return rows?.map((row) => {
            let componentResults = [];
            const { component_results: _component_results, ...rest } = row;
            try {
                componentResults = _component_results ? JSON.parse(_component_results) : [];
            }
            catch {
                componentResults = [];
            }
            return {
                ...rest,
                componentResults,
            };
        });
    }
    /**
     * Aggregate dashboard metrics for Guardrails dashboard widgets.
     * Returns structures matching the mock useGuardRailsDashboard hook.
     * This is an internal helper; public methods below expose per-widget data.
     */
    static async aggregateDashboardMetrics(params) {
        const { applicationId, projectId, start, end, guardrailType = 'all', version, environment, } = params;
        await GuardRailsService.ensureGuardrailResultsTable();
        const whereParts = [
            'application_id = {applicationId:String}',
            'project_id = {projectId:String}',
            'created_at BETWEEN parseDateTimeBestEffort({start:String}) AND parseDateTimeBestEffort({end:String})',
        ];
        if (guardrailType && guardrailType !== 'all') {
            whereParts.push('guardrail_stage = {guardrailStage:String}');
        }
        if (version) {
            whereParts.push('app_version = {version:String}');
        }
        if (environment) {
            whereParts.push('environment = {environment:String}');
        }
        const sql = `
      SELECT
        guardrail_stage,
        latency,
        pass,
        error,
        component_results,
        final_content,
        created_at
      FROM ${GuardRailsService.GUARDRAIL_RESULTS_TABLE}
      WHERE ${whereParts.join(' AND ')}
      ORDER BY created_at ASC
    `;
        const rows = await clickhouseService_1.clickHouseService.query(sql, {
            applicationId,
            projectId,
            start,
            end,
            guardrailStage: guardrailType,
            version: version ?? '',
            environment: environment ?? '',
        });
        const parsed = rows?.map((row) => {
            let componentResults = [];
            try {
                componentResults = row.component_results ? JSON.parse(row.component_results) : [];
            }
            catch {
                componentResults = [];
            }
            const createdAt = new Date(row.created_at);
            const latency = Number(row.latency ?? 0);
            const stage = row.guardrail_stage || 'input';
            const isPass = row.pass === true ||
                row.pass === '1' ||
                row.pass === 1 ||
                row.pass === 'true';
            return {
                guardrail_stage: stage,
                latency,
                pass: isPass,
                error: row.error,
                componentResults,
                created_at: createdAt,
            };
        });
        // Total requests and validations per day
        const totalRequestsMap = new Map();
        // Latency per day & stage
        const latencyPerDay = {};
        // Latency distributions
        const inputLatency = [];
        const outputLatency = [];
        // Exceptions over time (by day)
        const exceptionsPerDay = {};
        // Category failure data
        const categoryMap = {};
        let inputTotalRequests = 0;
        let outputTotalRequests = 0;
        let inputTotalExceptions = 0;
        let outputTotalExceptions = 0;
        let inputValidationChecks = 0;
        let outputValidationChecks = 0;
        parsed.forEach((row) => {
            const day = GuardRailsService.dayKey(row.created_at);
            const stage = (row.guardrail_stage === 'output' ? 'output' : 'input');
            if (stage === 'input') {
                inputTotalRequests += 1;
            }
            else {
                outputTotalRequests += 1;
            }
            // Total requests / validations
            const dayEntry = totalRequestsMap.get(day) || { requests: 0, validations: 0 };
            dayEntry.requests += 1;
            dayEntry.validations += row?.componentResults?.length ?? 0;
            totalRequestsMap.set(day, dayEntry);
            // Latency
            if (!latencyPerDay[day]) {
                latencyPerDay[day] = { input: [], output: [] };
            }
            if (row.latency != null) {
                latencyPerDay[day][stage].push(row.latency);
                if (stage === 'input') {
                    inputLatency.push(row.latency);
                }
                else {
                    outputLatency.push(row.latency);
                }
            }
            const isException = !row.pass || row.error;
            if (isException) {
                if (stage === 'input') {
                    inputTotalExceptions += 1;
                }
                else {
                    outputTotalExceptions += 1;
                }
                // Exceptions over time by day
                if (!exceptionsPerDay[day]) {
                    const ts = new Date(row.created_at);
                    ts.setUTCHours(0, 0, 0, 0);
                    exceptionsPerDay[day] = {
                        date: day,
                        timestamp: ts,
                        input: 0,
                        output: 0,
                    };
                }
                if (stage === 'input') {
                    exceptionsPerDay[day].input += 1;
                }
                else {
                    exceptionsPerDay[day].output += 1;
                }
            }
            // Category failure analysis from componentResults
            row.componentResults.forEach((component) => {
                if (stage === 'input') {
                    inputValidationChecks += 1;
                }
                else {
                    outputValidationChecks += 1;
                }
                const name = component?.assertion?.displayLabel ||
                    component?.assertion?.type ||
                    'Unknown';
                const key = `${name}::${stage}`;
                if (!categoryMap[key]) {
                    categoryMap[key] = {
                        name,
                        type: stage,
                        validationVolume: 0,
                        failedVolume: 0,
                    };
                }
                categoryMap[key].validationVolume += 1;
                if (!component.pass || row.error) {
                    categoryMap[key].failedVolume += 1;
                }
            });
        });
        // Build arrays
        const totalRequestsData = Array.from(totalRequestsMap.entries())
            .sort(([a], [b]) => (a < b ? -1 : 1))
            .map(([day, v]) => ({
            timestamp: new Date(`${day}T00:00:00Z`),
            requests: v.requests,
            validations: v.validations,
        }));
        const guardrailLatencyData = Object.entries(latencyPerDay)
            .sort(([a], [b]) => (a < b ? -1 : 1))
            .map(([day, values]) => {
            const inputVals = values.input;
            const outputVals = values.output;
            return {
                timestamp: new Date(`${day}T00:00:00Z`),
                inputAvg: inputVals.length
                    ? inputVals.reduce((s, v) => s + v, 0) / inputVals.length
                    : 0,
                inputP60: GuardRailsService.percentile(inputVals, 60),
                inputP90: GuardRailsService.percentile(inputVals, 90),
                inputP99: GuardRailsService.percentile(inputVals, 99),
                outputAvg: outputVals.length
                    ? outputVals.reduce((s, v) => s + v, 0) / outputVals.length
                    : 0,
                outputP60: GuardRailsService.percentile(outputVals, 60),
                outputP90: GuardRailsService.percentile(outputVals, 90),
                outputP99: GuardRailsService.percentile(outputVals, 99),
            };
        });
        const summaryData = {
            all: {
                totalRequests: inputTotalRequests + outputTotalRequests,
                validationChecks: inputValidationChecks + outputValidationChecks,
                exceptions: inputTotalExceptions + outputTotalExceptions,
                exceptionsPercentage: inputTotalExceptions + outputTotalExceptions > 0
                    ? ((inputTotalExceptions + outputTotalExceptions) /
                        (inputTotalRequests + outputTotalRequests)) *
                        100
                    : 0,
            },
            input: {
                totalRequests: inputTotalRequests,
                validationChecks: inputValidationChecks,
                exceptions: inputTotalExceptions,
                exceptionsPercentage: inputTotalExceptions > 0
                    ? (inputTotalExceptions / inputTotalRequests) * 100
                    : 0,
            },
            output: {
                totalRequests: outputTotalRequests,
                validationChecks: outputValidationChecks,
                exceptions: outputTotalExceptions,
                exceptionsPercentage: outputTotalExceptions > 0 ? (outputTotalExceptions / outputTotalRequests) * 100 : 0,
            },
        };
        const exceptionsOverTimeData = Object.values(exceptionsPerDay).sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
        const categoryFailureData = Object.values(categoryMap).map((cat) => ({
            ...cat,
            failurePercentage: cat.validationVolume > 0
                ? (cat.failedVolume / cat.validationVolume) * 100
                : 0,
        }));
        return {
            totalRequestsData,
            guardrailLatencyData,
            latencyHistogramData: {
                input: inputLatency,
                output: outputLatency,
            },
            summaryData,
            exceptionsOverTimeData,
            categoryFailureData,
            pieChartData: {
                total: inputTotalRequests + outputTotalRequests,
                errors: parsed?.filter((row) => row.error !== null && row.error !== undefined && row.error !== '')?.length ?? 0,
            },
        };
    }
    // Public helpers for individual widgets
    static async getTotalRequestsMetrics(params) {
        const base = await GuardRailsService.aggregateDashboardMetrics(params);
        return {
            totalRequestsData: base.totalRequestsData,
        };
    }
    static async getLatencyMetrics(params) {
        const base = await GuardRailsService.aggregateDashboardMetrics(params);
        return {
            guardrailLatencyData: base.guardrailLatencyData,
        };
    }
    static async getLatencyHistogramMetrics(params) {
        const base = await GuardRailsService.aggregateDashboardMetrics(params);
        return {
            latencyHistogramData: base.latencyHistogramData,
        };
    }
    static async getAnalysisMetrics(params) {
        const base = await GuardRailsService.aggregateDashboardMetrics(params);
        return {
            summaryData: base.summaryData,
            exceptionsOverTimeData: base.exceptionsOverTimeData,
            categoryFailureData: base.categoryFailureData,
        };
    }
    static async getRequestSummaryMetrics(params) {
        const base = await GuardRailsService.aggregateDashboardMetrics(params);
        return base.pieChartData;
    }
    /**
     * Fetch only summary metrics (totalRequests, validationChecks, exceptions) via lightweight aggregation.
     */
    static async getSummaryMetricsOnly(params) {
        const { applicationId, projectId, start, end, guardrailType = 'all', version, environment, } = params;
        await GuardRailsService.ensureGuardrailResultsTable();
        const whereParts = [
            'application_id = {applicationId:String}',
            'project_id = {projectId:String}',
            'created_at BETWEEN parseDateTimeBestEffort({start:String}) AND parseDateTimeBestEffort({end:String})',
        ];
        if (guardrailType && guardrailType !== 'all') {
            whereParts.push('guardrail_stage = {guardrailStage:String}');
        }
        if (version) {
            whereParts.push('app_version = {version:String}');
        }
        if (environment) {
            whereParts.push('environment = {environment:String}');
        }
        const sql = `
      SELECT
        guardrail_stage,
        count() as requests,
        countIf(NOT pass OR pass = false OR length(toString(error)) > 0) as exceptions,
        sum(JSONLength(component_results)) as validation_checks
      FROM ${GuardRailsService.GUARDRAIL_RESULTS_TABLE}
      WHERE ${whereParts.join(' AND ')}
      GROUP BY guardrail_stage
    `;
        const queryParams = {
            applicationId,
            projectId,
            start,
            end,
            version: version ?? '',
            environment: environment ?? '',
        };
        if (guardrailType && guardrailType !== 'all') {
            queryParams.guardrailStage = guardrailType;
        }
        const rows = await clickhouseService_1.clickHouseService.query(sql, queryParams);
        let inputRequests = 0;
        let inputExceptions = 0;
        let inputValidationChecks = 0;
        let outputRequests = 0;
        let outputExceptions = 0;
        let outputValidationChecks = 0;
        rows?.forEach((row) => {
            const stage = (row.guardrail_stage === 'output' ? 'output' : 'input');
            const requests = Number(row.requests ?? 0);
            const exceptions = Number(row.exceptions ?? 0);
            const vc = Number(row.validation_checks ?? 0);
            if (stage === 'input') {
                inputRequests = requests;
                inputExceptions = exceptions;
                inputValidationChecks = vc;
            }
            else {
                outputRequests = requests;
                outputExceptions = exceptions;
                outputValidationChecks = vc;
            }
        });
        const totalRequests = inputRequests + outputRequests;
        const totalExceptions = inputExceptions + outputExceptions;
        const totalValidationChecks = inputValidationChecks + outputValidationChecks;
        const summaryData = {
            all: {
                totalRequests,
                validationChecks: totalValidationChecks,
                exceptions: totalExceptions,
                exceptionsPercentage: totalExceptions > 0 ? (totalExceptions / totalRequests) * 100 : 0,
            },
            input: {
                totalRequests: inputRequests,
                validationChecks: inputValidationChecks,
                exceptions: inputExceptions,
                exceptionsPercentage: inputExceptions > 0 ? (inputExceptions / inputRequests) * 100 : 0,
            },
            output: {
                totalRequests: outputRequests,
                validationChecks: outputValidationChecks,
                exceptions: outputExceptions,
                exceptionsPercentage: outputExceptions > 0 ? (outputExceptions / outputRequests) * 100 : 0,
            },
        };
        return { summaryData };
    }
    /**
     * Fetch only exceptions-over-time (by month) via lightweight aggregation.
     */
    static async getExceptionsOverTimeOnly(params) {
        const { applicationId, projectId, start, end, guardrailType = 'all', version, environment, } = params;
        await GuardRailsService.ensureGuardrailResultsTable();
        const whereParts = [
            'application_id = {applicationId:String}',
            'project_id = {projectId:String}',
            'created_at BETWEEN parseDateTimeBestEffort({start:String}) AND parseDateTimeBestEffort({end:String})',
            '(NOT pass OR pass = false OR length(toString(error)) > 0)',
        ];
        if (guardrailType && guardrailType !== 'all') {
            whereParts.push('guardrail_stage = {guardrailStage:String}');
        }
        if (version) {
            whereParts.push('app_version = {version:String}');
        }
        if (environment) {
            whereParts.push('environment = {environment:String}');
        }
        const sql = `
      SELECT
        toStartOfMonth(created_at) as month_ts,
        guardrail_stage,
        count() as cnt
      FROM ${GuardRailsService.GUARDRAIL_RESULTS_TABLE}
      WHERE ${whereParts.join(' AND ')}
      GROUP BY month_ts, guardrail_stage
      ORDER BY month_ts ASC
    `;
        const queryParams = {
            applicationId,
            projectId,
            start,
            end,
            version: version ?? '',
            environment: environment ?? '',
        };
        if (guardrailType && guardrailType !== 'all') {
            queryParams.guardrailStage = guardrailType;
        }
        const rows = await clickhouseService_1.clickHouseService.query(sql, queryParams);
        const byMonth = {};
        rows?.forEach((row) => {
            const ts = row.month_ts ? new Date(row.month_ts) : new Date();
            const key = String(row.month_ts ?? ts.getTime());
            const monthLabel = `${String(ts.getUTCMonth() + 1).padStart(2, '0')}/${String(ts.getUTCFullYear()).slice(2)}`;
            const stage = row.guardrail_stage === 'output' ? 'output' : 'input';
            const cnt = Number(row.cnt ?? 0);
            if (!byMonth[key]) {
                byMonth[key] = {
                    month: monthLabel,
                    timestamp: ts,
                    input: 0,
                    output: 0,
                };
            }
            byMonth[key][stage] = cnt;
        });
        const exceptionsOverTimeData = Object.values(byMonth).sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
        return { exceptionsOverTimeData };
    }
    /**
     * Fetch only category failure metrics via aggregation over component_results.
     */
    static async getCategoryFailureMetricsOnly(params) {
        const { applicationId, projectId, start, end, guardrailType = 'all', version, environment, } = params;
        await GuardRailsService.ensureGuardrailResultsTable();
        const whereParts = [
            'application_id = {applicationId:String}',
            'project_id = {projectId:String}',
            'created_at BETWEEN parseDateTimeBestEffort({start:String}) AND parseDateTimeBestEffort({end:String})',
        ];
        if (guardrailType && guardrailType !== 'all') {
            whereParts.push('guardrail_stage = {guardrailStage:String}');
        }
        if (version) {
            whereParts.push('app_version = {version:String}');
        }
        if (environment) {
            whereParts.push('environment = {environment:String}');
        }
        const sql = `
      SELECT
        guardrail_stage,
        coalesce(
          nullIf(JSONExtractString(component, 'assertion', 'displayLabel'), ''),
          JSONExtractString(component, 'assertion', 'type'),
          'Unknown'
        ) as name,
        count() as validationVolume,
        countIf(NOT JSONExtractBool(component, 'pass') OR length(toString(error)) > 0) as failedVolume
      FROM ${GuardRailsService.GUARDRAIL_RESULTS_TABLE}
      ARRAY JOIN JSONExtractArrayRaw(component_results) AS component
      WHERE ${whereParts.join(' AND ')}
      GROUP BY guardrail_stage, name
    `;
        const queryParams = {
            applicationId,
            projectId,
            start,
            end,
            version: version ?? '',
            environment: environment ?? '',
        };
        if (guardrailType && guardrailType !== 'all') {
            queryParams.guardrailStage = guardrailType;
        }
        const rows = await clickhouseService_1.clickHouseService.query(sql, queryParams);
        const categoryFailureData = (rows ?? []).map((row) => {
            const validationVolume = Number(row.validationVolume ?? 0);
            const failedVolume = Number(row.failedVolume ?? 0);
            return {
                name: String(row.name ?? 'Unknown'),
                type: (row.guardrail_stage === 'output' ? 'output' : 'input'),
                validationVolume,
                failedVolume,
                failurePercentage: validationVolume > 0 ? (failedVolume / validationVolume) * 100 : 0,
            };
        });
        return { categoryFailureData };
    }
    static async getLatencyByValidatorMetrics(params) {
        const { applicationId, projectId, start, end, guardrailType = 'all', version, environment, } = params;
        await GuardRailsService.ensureGuardrailResultsTable();
        const whereParts = [
            'application_id = {applicationId:String}',
            'project_id = {projectId:String}',
            'created_at BETWEEN parseDateTimeBestEffort({start:String}) AND parseDateTimeBestEffort({end:String})',
        ];
        if (guardrailType && guardrailType !== 'all') {
            whereParts.push('guardrail_stage = {guardrailStage:String}');
        }
        if (version) {
            whereParts.push('app_version = {version:String}');
        }
        if (environment) {
            whereParts.push('environment = {environment:String}');
        }
        // Query to get latency data grouped by validator (from component_results)
        const sql = `
      SELECT
        toDate(created_at) as day,
        guardrail_stage,
        component,
        toFloat64OrZero(JSONExtractString(component, 'latency')) as component_latency
      FROM ${GuardRailsService.GUARDRAIL_RESULTS_TABLE}
      ARRAY JOIN JSONExtractArrayRaw(component_results) AS component
      WHERE ${whereParts.join(' AND ')}
        AND toFloat64OrZero(JSONExtractString(component, 'latency')) > 0
      ORDER BY day ASC
    `;
        const queryParams = {
            applicationId,
            projectId,
            start,
            end,
            version: version ?? '',
            environment: environment ?? '',
        };
        if (guardrailType && guardrailType !== 'all') {
            queryParams.guardrailStage = guardrailType;
        }
        const rows = await clickhouseService_1.clickHouseService.query(sql, queryParams);
        // Parse and group by validator and day
        const validatorLatencyMap = {};
        rows?.forEach((row) => {
            let component = {};
            try {
                component = row.component ? JSON.parse(row.component) : {};
            }
            catch {
                component = {};
            }
            const validatorName = component?.assertion?.displayLabel ||
                component?.assertion?.type ||
                'Unknown';
            // Group by validator name only, not by stage
            const validatorKey = validatorName;
            const day = row.day;
            const latency = Number(row.component_latency ?? 0);
            if (latency > 0) {
                if (!validatorLatencyMap[validatorKey]) {
                    validatorLatencyMap[validatorKey] = {};
                }
                if (!validatorLatencyMap[validatorKey][day]) {
                    validatorLatencyMap[validatorKey][day] = [];
                }
                validatorLatencyMap[validatorKey][day].push(latency);
            }
        });
        // Get all unique days
        const allDays = new Set();
        Object.values(validatorLatencyMap).forEach((dayMap) => {
            Object.keys(dayMap).forEach((day) => allDays.add(day));
        });
        const sortedDays = Array.from(allDays).sort();
        // Build time series data for each validator
        const validatorData = [];
        Object.entries(validatorLatencyMap).forEach(([validatorKey, dayMap]) => {
            const validatorName = validatorKey;
            const allLatencies = [];
            const timeSeriesData = sortedDays.map((day) => {
                const latencies = dayMap[day] || [];
                allLatencies.push(...latencies);
                return {
                    timestamp: new Date(`${day}T00:00:00Z`),
                    avg: latencies.length
                        ? latencies.reduce((s, v) => s + v, 0) / latencies.length
                        : 0,
                    p60: GuardRailsService.percentile(latencies, 60),
                    p90: GuardRailsService.percentile(latencies, 90),
                    p99: GuardRailsService.percentile(latencies, 99),
                };
            });
            const averageLatency = allLatencies.length
                ? allLatencies.reduce((s, v) => s + v, 0) / allLatencies.length
                : 0;
            validatorData.push({
                validatorName,
                stage: 'all', // Combined input and output
                data: timeSeriesData,
                averageLatency,
            });
        });
        // Sort by average latency (descending) to show slowest validators first
        validatorData.sort((a, b) => b.averageLatency - a.averageLatency);
        return {
            latencyByValidatorData: validatorData,
        };
    }
    static async getValidationFailures(params) {
        const { applicationId, projectId, start, end, guardrailType, version, environment, search, page, limit, sortBy = 'recent', category, stage, passStatus, } = params;
        await GuardRailsService.ensureGuardrailResultsTable();
        const whereParts = [
            'application_id = {applicationId:String}',
            'project_id = {projectId:String}',
            'created_at BETWEEN parseDateTimeBestEffort({start:String}) AND parseDateTimeBestEffort({end:String})',
        ];
        // Stage filter (overrides guardrailType if provided)
        const stageFilter = stage || (guardrailType && guardrailType !== 'all' ? guardrailType : null);
        if (lodash_1.default.isEmpty(stageFilter) === false) {
            whereParts.push('guardrail_stage = {guardrailStage:String}');
        }
        if (lodash_1.default.isEmpty(version) === false) {
            whereParts.push('app_version = {version:String}');
        }
        if (lodash_1.default.isEmpty(environment) === false) {
            whereParts.push('environment = {environment:String}');
        }
        if (lodash_1.default.isEmpty(search) === false) {
            whereParts.push('(prompt LIKE {search:String} OR response LIKE {search:String} OR toString(id) LIKE {search:String})');
        }
        // Filter based on passStatus: 'success' (all passed) or 'error' (has failures)
        let passStatusFilter = '';
        if (passStatus === 'success') {
            // For success: error is empty string
            passStatusFilter = `(error = '' OR error IS NULL)`;
        }
        else if (passStatus === 'error') {
            // For error: pass = false OR has failed components (default behavior)
            passStatusFilter = `(pass = false AND  error != '' AND score = 0)`;
        }
        else {
            // Default: filter for rows that have at least one failed component (backward compatibility)
            passStatusFilter = `arrayExists(x -> JSONExtractBool(x, 'pass') = false, JSONExtractArrayRaw(component_results))`;
        }
        // Category filter - check if any component matches the category
        if (lodash_1.default.isEmpty(category) === false) {
            passStatusFilter = `arrayExists(x -> JSONExtractBool(x, 'pass') = false AND JSONExtractString(x, 'assertion', 'type') = {category:String}, JSONExtractArrayRaw(component_results))`;
        }
        // Sort order
        let orderBy = 'created_at DESC'; // Default: recent
        if (sortBy === 'oldest') {
            orderBy = 'created_at ASC';
        }
        else if (sortBy === 'fast_latency') {
            orderBy = 'latency ASC NULLS LAST, created_at DESC';
        }
        else if (sortBy === 'low_latency') {
            orderBy = 'latency DESC NULLS LAST, created_at DESC';
        }
        const sql = `
    SELECT
        id,
        created_at,
        component_results,
        guardrail_stage,
        prompt,
        response,
        pass,
        latency,
        app_version,
        environment,
        final_content
      FROM ${GuardRailsService.GUARDRAIL_RESULTS_TABLE}
      WHERE ${whereParts.join(' AND ')}
        AND ${passStatusFilter}
      ORDER BY ${orderBy}
      ${page !== undefined && limit !== undefined ? `LIMIT {limit:UInt64} OFFSET {offset:UInt64}` : ''}
    `;
        const queryParams = {
            applicationId,
            projectId,
            start,
            end,
            guardrailStage: stageFilter ?? '',
            version: version ?? '',
            environment: environment ?? '',
            search: search ? `%${search}%` : '',
            category: category ?? '',
        };
        if (page !== undefined && limit !== undefined) {
            queryParams.limit = limit;
            queryParams.offset = (page - 1) * limit;
        }
        const rows = await clickhouseService_1.clickHouseService.query(sql, queryParams);
        // Count query - count distinct guardrail results that have failed components
        const countSql = `
      SELECT COUNT(DISTINCT id) AS total
      FROM ${GuardRailsService.GUARDRAIL_RESULTS_TABLE}
      WHERE ${whereParts.join(' AND ')}
        AND ${passStatusFilter}
    `;
        const total = await clickhouseService_1.clickHouseService.query(countSql, {
            applicationId,
            projectId,
            start,
            end,
            guardrailStage: stageFilter ?? '',
            version: version ?? '',
            environment: environment ?? '',
            search: search ? `%${search}%` : '',
            category: category ?? '',
        });
        // Process rows to extract failed components and filter by category if needed
        const processedRows = rows?.map((row) => {
            let componentResults = [];
            try {
                componentResults = row.component_results ? JSON.parse(row.component_results) : [];
            }
            catch {
                componentResults = [];
            }
            // For success requests, show all components (all passed)
            // For error requests, filter to only failed components
            let displayComponents = componentResults;
            if (passStatus === 'error' || !passStatus) {
                displayComponents = componentResults.filter((comp) => comp.pass === false);
                // Apply category filter if specified
                if (category) {
                    displayComponents = displayComponents.filter((comp) => comp?.assertion?.type === category);
                }
            }
            else if (passStatus === 'success' && category) {
                // For success with category, filter to components matching category
                displayComponents = componentResults.filter((comp) => comp?.assertion?.type === category && comp.pass === true);
            }
            // Get the first component for display in table (for backward compatibility)
            const firstComponent = displayComponents[0] || null;
            return {
                id: row.id,
                created_at: row.created_at,
                guardrail_stage: row.guardrail_stage,
                prompt: row.prompt,
                response: row.response,
                latency: row.latency,
                app_version: row.app_version,
                environment: row.environment,
                final_content: row.final_content,
                component: firstComponent, // For backward compatibility with table display
                componentResults, // All components
                failedComponents: passStatus === 'error' || !passStatus ? displayComponents : [], // Only failed components (for error) or empty (for success)
            };
        }) ?? [];
        return {
            total: total[0]?.total ?? 0,
            page: page ?? 1,
            limit: limit ?? 10,
            failuresData: processedRows,
        };
    }
    static async getAllValidationFailuresForDownload(params) {
        const { applicationId, projectId, start, end, guardrailType, version, environment, search, sortBy = 'recent', category, stage, } = params;
        await GuardRailsService.ensureGuardrailResultsTable();
        const whereParts = [
            'application_id = {applicationId:String}',
            'project_id = {projectId:String}',
            'created_at BETWEEN parseDateTimeBestEffort({start:String}) AND parseDateTimeBestEffort({end:String})',
        ];
        const stageFilter = stage || (guardrailType && guardrailType !== 'all' ? guardrailType : null);
        if (stageFilter) {
            whereParts.push('guardrail_stage = {guardrailStage:String}');
        }
        if (version) {
            whereParts.push('app_version = {version:String}');
        }
        if (environment) {
            whereParts.push('environment = {environment:String}');
        }
        if (lodash_1.default.isEmpty(search) === false) {
            whereParts.push('(prompt LIKE {search:String} OR response LIKE {search:String} OR toString(id) LIKE {search:String})');
        }
        // Filter for rows that have at least one failed component
        // Category filter - check if any component matches the category and has pass = false
        let hasFailedComponentsFilter = `arrayExists(x -> JSONExtractBool(x, 'pass') = false, JSONExtractArrayRaw(component_results))`;
        if (category) {
            hasFailedComponentsFilter = `arrayExists(x -> JSONExtractBool(x, 'pass') = false AND JSONExtractString(x, 'assertion', 'type') = {category:String}, JSONExtractArrayRaw(component_results))`;
        }
        // Sort order
        let orderBy = 'created_at DESC';
        if (sortBy === 'oldest') {
            orderBy = 'created_at ASC';
        }
        else if (sortBy === 'fast_latency') {
            orderBy = 'latency ASC NULLS LAST, created_at DESC';
        }
        else if (sortBy === 'low_latency') {
            orderBy = 'latency DESC NULLS LAST, created_at DESC';
        }
        const sql = `
    SELECT
        id,
        created_at,
        component_results,
        guardrail_stage,
        prompt,
        response,
        latency,
        app_version,
        environment,
        final_content
      FROM ${GuardRailsService.GUARDRAIL_RESULTS_TABLE}
      WHERE ${whereParts.join(' AND ')}
        AND ${hasFailedComponentsFilter}
      ORDER BY ${orderBy}
    `;
        const rows = await clickhouseService_1.clickHouseService.query(sql, {
            applicationId,
            projectId,
            start,
            end,
            guardrailStage: stageFilter ?? '',
            version: version ?? '',
            environment: environment ?? '',
            search: search ? `%${search}%` : '',
            category: category ?? '',
        });
        // Process rows to extract failed components and filter by category if needed
        const processedRows = rows?.map((row) => {
            let componentResults = [];
            try {
                componentResults = row.component_results ? JSON.parse(row.component_results) : [];
            }
            catch {
                componentResults = [];
            }
            // Filter to only failed components
            let failedComponents = componentResults.filter((comp) => comp.pass === false);
            // Apply category filter if specified
            if (category) {
                failedComponents = failedComponents.filter((comp) => comp?.assertion?.type === category);
            }
            // Get the first failed component for display in CSV (for backward compatibility)
            const firstFailedComponent = failedComponents[0] || null;
            return {
                id: row.id,
                created_at: row.created_at,
                guardrail_stage: row.guardrail_stage,
                prompt: row.prompt || '',
                response: row.response || '',
                latency: row.latency || 0,
                app_version: row.app_version || '',
                environment: row.environment || '',
                final_content: row.final_content || '',
                component: firstFailedComponent, // For backward compatibility
                componentResults, // All components
                failedComponents, // Only failed components
            };
        }) ?? [];
        return processedRows;
    }
    static async getValidationFailureDetails(params) {
        const { id, applicationId, projectId } = params;
        await GuardRailsService.ensureGuardrailResultsTable();
        // Only fetch fields needed for UI display
        const sql = `
      SELECT
        id,
        guardrail_stage,
        prompt,
        response,
        latency,
        component_results,
        app_version,
        environment,
        final_content,
        created_at
      FROM ${GuardRailsService.GUARDRAIL_RESULTS_TABLE}
      WHERE id = {id:String}
        AND application_id = {applicationId:String}
        AND project_id = {projectId:String}
      LIMIT 1
    `;
        const rows = await clickhouseService_1.clickHouseService.query(sql, {
            id,
            applicationId,
            projectId,
        });
        if (!rows || rows.length === 0) {
            return null;
        }
        const row = rows[0];
        let componentResults = [];
        try {
            componentResults = row.component_results ? JSON.parse(row.component_results) : [];
        }
        catch {
            componentResults = [];
        }
        // Pricing configuration
        const inputTokensPerUnit = 1000000;
        const outputTokensPerUnit = 1000000;
        const inputPricePerUnit = 0.125;
        const outputPricePerUnit = 1.25;
        // Calculate cost for each component that has tokensUsed
        const componentsWithCost = componentResults.map((component) => {
            if (component?.tokensUsed) {
                const promptTokens = component.tokensUsed.prompt || 0;
                const completionTokens = component.tokensUsed.completion || 0;
                // Calculate cost: (tokens / tokens_per_unit) * price_per_unit
                const inputCost = (promptTokens / inputTokensPerUnit) * inputPricePerUnit;
                const outputCost = (completionTokens / outputTokensPerUnit) * outputPricePerUnit;
                const totalCost = inputCost + outputCost;
                return {
                    ...component,
                    cost: totalCost,
                    costBreakdown: {
                        inputCost,
                        outputCost,
                        promptTokens,
                        completionTokens,
                    },
                };
            }
            return component;
        });
        return {
            id: row.id,
            guardrail_stage: row.guardrail_stage,
            prompt: row.prompt || '',
            response: row.response || '',
            latency: row.latency || 0,
            app_version: row.app_version || '',
            environment: row.environment || '',
            final_content: row.final_content || '',
            created_at: row.created_at,
            cost: componentsWithCost.reduce((acc, component) => acc + (component.cost || 0), 0),
            allComponents: componentsWithCost, // All components for sequential flow display with cost
        };
    }
    static async getApplicationGaurdRailConfig(applicationId, projectId, name) {
        const db = await (0, database_1.getDb)();
        let application;
        if (applicationId) {
            [application] = await db
                .select()
                .from(tables_1.applicationsTable)
                .where((0, drizzle_orm_1.eq)(tables_1.applicationsTable.id, applicationId))
                .limit(1);
        }
        else if (name) {
            [application] = await db
                .select()
                .from(tables_1.applicationsTable)
                .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(tables_1.applicationsTable.name, name), (0, drizzle_orm_1.eq)(tables_1.applicationsTable.projectId, projectId)))
                .limit(1);
        }
        else {
            throw new Error('Either applicationId or name must be provided');
        }
        if (!application) {
            throw new Error('Application not found');
        }
        // Check if starter plan has reached guardrails limit
        const workspaceId = application.workspaceId;
        if (workspaceId) {
            try {
                const upgradeMeta = await (0, get_upgrade_meta_1.getUpgradeMetaByOrgId)(workspaceId, {
                    fetchGuardrailsUsage: true,
                });
                const { isEnterprise, isStarter, usages, restrictionsData, notifications, } = upgradeMeta;
                const isProfessional = !isEnterprise && !isStarter; // Professional plan is neither enterprise nor starter
                const exhaustedNotificationMap = new Map();
                // Check guardrails restrictions using common function
                const shouldBlock = await GuardRailsService.checkGuardrailsRestrictions({
                    workspaceId,
                    isEnterprise,
                    isStarter,
                    isProfessional,
                    usages,
                    restrictionsData,
                    notifications,
                    exhaustedNotificationMap,
                });
                if (shouldBlock) {
                    throw new Error(restriction_message_1.ERROR_RESTRICTION_MESSAGE.guardrails.title);
                }
            }
            catch (error) {
                // If it's the limit reached error, re-throw it
                if (error?.message?.includes('guardrails limit') || error?.message?.includes('Guardrails limit')) {
                    throw error;
                }
                // For other errors, log but continue (don't block config retrieval)
                logger_1.logger.error(`Error checking guardrails limit: ${error}`);
            }
        }
        // Fetch guardrails configuration
        const [guardrailsConfig] = await db
            .select()
            .from(tables_1.applicationGuardrailsTable)
            .where((0, drizzle_orm_1.eq)(tables_1.applicationGuardrailsTable.applicationId, application.id))
            .limit(1);
        logger_1.logger.info(`Fetched application ${applicationId || name} Gaurd Rail config`);
        return {
            id: guardrailsConfig?.id,
            application_id: application?.id,
            enable_guard_rails: guardrailsConfig?.enable_guard_rails || false,
            input_validators: guardrailsConfig.input_validators || [],
            output_validators: guardrailsConfig.output_validators || [],
        };
    }
}
exports.GuardRailsService = GuardRailsService;
GuardRailsService.GUARDRAIL_RESULTS_TABLE = 'guardrail_results';
//# sourceMappingURL=gaurdRailsService.js.map