"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.applicationNameFromSecurityConfig = applicationNameFromSecurityConfig;
exports.buildLinkedApplicationIdMap = buildLinkedApplicationIdMap;
exports.linkedApplicationIdFromMap = linkedApplicationIdFromMap;
exports.applicationDefinitionHasNonEmptyValues = applicationDefinitionHasNonEmptyValues;
exports.resolveApplicationIdFromSecurityConfig = resolveApplicationIdFromSecurityConfig;
exports.createSecurityConfig = createSecurityConfig;
exports.updateSecurityConfig = updateSecurityConfig;
exports.getSecurityConfigByIdOrName = getSecurityConfigByIdOrName;
exports.getAllSecurityConfigs = getAllSecurityConfigs;
exports.deleteSecurityConfig = deleteSecurityConfig;
exports.archiveSecurityConfig = archiveSecurityConfig;
const drizzle_orm_1 = require("drizzle-orm");
const uuid_1 = require("uuid");
const database_1 = require("../../../database");
const tables_1 = require("../../../database/tables");
const cosmosDbRetry_1 = require("../../../util/cosmosDbRetry");
const logger_1 = __importDefault(require("../../../logger"));
const lodash_1 = __importDefault(require("lodash"));
const securityConfigArgusTools_1 = require("../../services/argus/tools/securityConfigArgusTools");
function applicationNameFromSecurityConfig(config) {
    if (!config || typeof config !== 'object') {
        return undefined;
    }
    const label = config.target?.label;
    return typeof label === 'string' && label.trim().length > 0 ? label.trim() : undefined;
}
/**
 * Collects unique (projectId, application name) pairs from rows, loads matching
 * `applications` rows in one query, returns map key `${projectId}::${name}` → application id.
 */
async function buildLinkedApplicationIdMap(db, rows) {
    const seenKeys = new Set();
    const pairs = [];
    for (const row of rows) {
        const name = applicationNameFromSecurityConfig(row.config);
        const projectId = row.projectId;
        if (!name || !projectId) {
            continue;
        }
        const key = `${projectId}::${name}`;
        if (seenKeys.has(key)) {
            continue;
        }
        seenKeys.add(key);
        pairs.push({ projectId, name });
    }
    const map = new Map();
    if (pairs.length === 0) {
        return map;
    }
    const conditions = pairs.map((p) => (0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(tables_1.applicationsTable.projectId, p.projectId), (0, drizzle_orm_1.eq)(tables_1.applicationsTable.name, p.name)));
    const apps = await db
        .select({
        id: tables_1.applicationsTable.id,
        name: tables_1.applicationsTable.name,
        projectId: tables_1.applicationsTable.projectId,
    })
        .from(tables_1.applicationsTable)
        .where((0, drizzle_orm_1.or)(...conditions))
        .execute();
    for (const app of apps) {
        if (app.projectId && app.name) {
            map.set(`${app.projectId}::${app.name}`, app.id);
        }
    }
    return map;
}
function linkedApplicationIdFromMap(map, projectId, config) {
    const name = applicationNameFromSecurityConfig(config);
    if (!name || !projectId) {
        return undefined;
    }
    return map.get(`${projectId}::${name}`);
}
/** True if `applicationDefinition` has at least one non-empty string field. */
function applicationDefinitionHasNonEmptyValues(applicationDefinition) {
    if (!applicationDefinition || typeof applicationDefinition !== 'object') {
        return false;
    }
    for (const v of Object.values(applicationDefinition)) {
        if (typeof v === 'string' && v.trim().length > 0) {
            return true;
        }
    }
    return false;
}
/**
 * Resolves the linked application id from `config.target.label` by matching
 * `applications.name` in the same project (same rules as `buildLinkedApplicationIdMap`).
 */
async function resolveApplicationIdFromSecurityConfig(db, projectId, cfg) {
    const label = applicationNameFromSecurityConfig(cfg);
    const pid = typeof projectId === 'string' ? projectId.trim() : '';
    if (!label || !pid) {
        return undefined;
    }
    const [row] = await db
        .select({ id: tables_1.applicationsTable.id })
        .from(tables_1.applicationsTable)
        .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(tables_1.applicationsTable.projectId, pid), (0, drizzle_orm_1.eq)(tables_1.applicationsTable.name, label)))
        .limit(1)
        .execute();
    return typeof row?.id === 'string' && row.id.trim() ? row.id.trim() : undefined;
}
async function createSecurityConfig(input) {
    const { name, status, config, projectId, orgId, applicationId } = input;
    if (!name || status === undefined || !projectId) {
        throw new Error('Missing required fields: name, status, projectId');
    }
    const db = await (0, database_1.getDb)();
    // Check for existing config with same name and projectId
    const existing = await db
        .select()
        .from(tables_1.securityConfigTable)
        .where((0, drizzle_orm_1.sql) `name = ${name} AND project_id = ${projectId}`)
        .execute();
    if (existing && existing.length > 0) {
        throw new Error('A security config with this name already exists for this project');
    }
    const id = (0, uuid_1.v4)();
    const finalConfig = config && typeof config === 'object' ? lodash_1.default.cloneDeep(config) : {};
    if (applicationId && typeof applicationId === 'string' && applicationId.trim() && projectId) {
        const [appRow] = await db
            .select({
            security_config: tables_1.applicationsTable.security_config,
            description: tables_1.applicationsTable.description,
            useCase: tables_1.applicationsTable.useCase,
        })
            .from(tables_1.applicationsTable)
            .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(tables_1.applicationsTable.id, applicationId.trim()), (0, drizzle_orm_1.eq)(tables_1.applicationsTable.projectId, projectId)))
            .limit(1);
        if (appRow) {
            const mergedAd = (0, securityConfigArgusTools_1.prefillApplicationDefinitionFromApplicationSecurityConfig)({
                incomingApplicationDefinition: finalConfig?.applicationDefinition,
                applicationSecurityConfig: appRow.security_config,
            });
            if (!mergedAd.purpose.trim()) {
                const d = typeof appRow.description === 'string' ? appRow.description.trim() : '';
                const u = typeof appRow.useCase === 'string' ? appRow.useCase.trim() : '';
                if (d) {
                    mergedAd.purpose = d;
                }
                else if (u) {
                    mergedAd.purpose = u;
                }
            }
            finalConfig.applicationDefinition = mergedAd;
            finalConfig.purpose = (0, securityConfigArgusTools_1.applicationDefinitionToPurpose)(mergedAd);
        }
    }
    await db.insert(tables_1.securityConfigTable).values({
        id,
        name,
        status,
        config: finalConfig,
        projectId,
        workspaceId: orgId,
        createdAt: new Date(),
        updatedAt: new Date(),
    });
    return { id };
}
async function updateSecurityConfig(input) {
    const { id, name, status, config, projectId, applicationId } = input;
    const db = await (0, database_1.getDb)();
    const [existing] = await db
        .select()
        .from(tables_1.securityConfigTable)
        .where((0, drizzle_orm_1.eq)(tables_1.securityConfigTable.id, id))
        .limit(1);
    if (!existing) {
        throw new Error('Security config not found');
    }
    // If name or projectId is being updated, check for duplicates
    if (name && projectId) {
        const dup = await db
            .select()
            .from(tables_1.securityConfigTable)
            .where((0, drizzle_orm_1.sql) `name = ${name} AND project_id = ${projectId} AND id != ${id}`)
            .execute();
        if (dup && dup.length > 0) {
            throw new Error('A security config with this name already exists for this project');
        }
    }
    const mergedConfig = config !== undefined && typeof config === 'object'
        ? lodash_1.default.merge(lodash_1.default.cloneDeep(existing.config && typeof existing.config === 'object' ? existing.config : {}), config)
        : existing.config;
    const effectiveProjectId = (typeof projectId === 'string' && projectId.trim()) ||
        (typeof existing.projectId === 'string' && existing.projectId.trim()) ||
        '';
    const explicitApplicationId = typeof applicationId === 'string' && applicationId.trim() ? applicationId.trim() : '';
    const effectiveApplicationId = explicitApplicationId ||
        (effectiveProjectId
            ? (await resolveApplicationIdFromSecurityConfig(db, effectiveProjectId, mergedConfig)) || ''
            : '');
    const applicationDefinition = mergedConfig
        ?.applicationDefinition;
    if (applicationDefinitionHasNonEmptyValues(applicationDefinition) &&
        effectiveApplicationId &&
        effectiveProjectId) {
        await db
            .update(tables_1.applicationsTable)
            .set({
            security_config: applicationDefinition,
            updatedAt: new Date(),
        })
            .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(tables_1.applicationsTable.id, effectiveApplicationId), (0, drizzle_orm_1.eq)(tables_1.applicationsTable.projectId, effectiveProjectId)))
            .execute();
    }
    const updateObj = { updatedAt: new Date() };
    if (name !== undefined) {
        updateObj.name = name;
    }
    if (status !== undefined) {
        updateObj.status = status;
    }
    if (config !== undefined) {
        updateObj.config = config;
    }
    if (projectId !== undefined) {
        updateObj.projectId = projectId;
    }
    if (applicationId !== undefined) {
        updateObj.applicationId = applicationId;
    }
    const result = await db
        .update(tables_1.securityConfigTable)
        .set(updateObj)
        .where((0, drizzle_orm_1.sql) `id = ${id}`)
        .execute();
    if (result.rowCount === 0) {
        throw new Error('Security config not found');
    }
    return { message: 'Security config updated' };
}
async function getSecurityConfigByIdOrName(params) {
    try {
        return await (0, cosmosDbRetry_1.retryWithCosmosDbBackoff)(async () => {
            const db = (0, database_1.getDb)();
            // Get the security config by id or name, but always require projectId to match
            let whereClause;
            if (params.id) {
                whereClause = (0, drizzle_orm_1.sql) `${tables_1.securityConfigTable.id} = ${params.id} AND ${tables_1.securityConfigTable.projectId} = ${params.projectId}`;
            }
            else if (params.name) {
                whereClause = (0, drizzle_orm_1.sql) `${tables_1.securityConfigTable.name} = ${params.name} AND ${tables_1.securityConfigTable.projectId} = ${params.projectId}`;
            }
            else {
                throw new Error('Either id or name must be provided');
            }
            const result = await db
                .select({
                id: tables_1.securityConfigTable.id,
                name: tables_1.securityConfigTable.name,
                status: tables_1.securityConfigTable.status,
                config: tables_1.securityConfigTable.config,
                projectId: tables_1.securityConfigTable.projectId,
                is_archived: tables_1.securityConfigTable.is_archived,
                createdAt: tables_1.securityConfigTable.createdAt,
                updatedAt: tables_1.securityConfigTable.updatedAt,
            })
                .from(tables_1.securityConfigTable)
                .where(whereClause)
                .execute();
            if (!result || result.length === 0) {
                throw new Error('Security config not found');
            }
            const config = result[0];
            // Get associated test run IDs
            const testRunRows = await db
                .select({ testRunId: tables_1.testRunsToSecurityConfigsTable.testRunId })
                .from(tables_1.testRunsToSecurityConfigsTable)
                .where((0, drizzle_orm_1.sql) `${tables_1.testRunsToSecurityConfigsTable.securityConfigId} = ${config.id}`)
                .execute();
            const testRunIds = testRunRows?.map((row) => row.testRunId);
            const applicationId = (await resolveApplicationIdFromSecurityConfig(db, params.projectId, config.config)) ??
                null;
            return { ...config, testRunIds, applicationId };
        });
    }
    catch (error) {
        if ((0, cosmosDbRetry_1.isCosmosDbRateLimitError)(error)) {
            logger_1.default.error(`Cosmos DB rate limit error when fetching security config. ` +
                `Error: ${error.message || error}`);
            throw new Error('Database rate limit exceeded. Please try again in a few moments. ' +
                'If this persists, consider increasing Cosmos DB Request Units (RUs).');
        }
        throw error;
    }
}
async function getAllSecurityConfigs(projectId, page, limit, archived_data_required, excludeCustomProviders, search) {
    const db = await (0, database_1.getDb)();
    let pagination;
    const includeArchived = archived_data_required !== 'false';
    // Build where conditions
    const whereConditions = [];
    if (projectId) {
        whereConditions.push((0, drizzle_orm_1.eq)(tables_1.securityConfigTable.projectId, projectId));
    }
    // Add archived filter if needed
    if (!includeArchived) {
        whereConditions.push((0, drizzle_orm_1.eq)(tables_1.securityConfigTable.is_archived, false));
    }
    const whereClause = whereConditions.length > 0 ? (0, drizzle_orm_1.and)(...whereConditions) : undefined;
    // Pagination
    let parsedLimit, parsedPage, offset;
    if (page !== undefined && limit !== undefined) {
        parsedLimit = Number(limit);
        parsedPage = Number(page);
        offset = (parsedPage - 1) * parsedLimit;
    }
    // Total count for pagination
    let totalCount = 0;
    if (parsedLimit !== undefined && parsedPage !== undefined) {
        const totalCountQuery = db.select({ count: (0, drizzle_orm_1.sql) `count(*)` }).from(tables_1.securityConfigTable);
        if (whereClause) {
            totalCountQuery.where(whereClause);
        }
        totalCount = await totalCountQuery.then((result) => Number(result[0].count));
    }
    // Main query with left joins
    const query = db
        .select({
        id: tables_1.securityConfigTable.id,
        name: tables_1.securityConfigTable.name,
        status: tables_1.securityConfigTable.status,
        config: tables_1.securityConfigTable.config,
        projectId: tables_1.securityConfigTable.projectId,
        is_archived: tables_1.securityConfigTable.is_archived,
        createdAt: tables_1.securityConfigTable.createdAt,
        securityEvaluationsCount: (0, drizzle_orm_1.sql) `COUNT(${tables_1.testRunsToSecurityConfigsTable.testRunId})::int`,
    })
        .from(tables_1.securityConfigTable)
        .leftJoin(tables_1.testRunsToSecurityConfigsTable, (0, drizzle_orm_1.eq)(tables_1.securityConfigTable.id, tables_1.testRunsToSecurityConfigsTable.securityConfigId))
        .groupBy(tables_1.securityConfigTable.id)
        .orderBy(tables_1.securityConfigTable.is_archived, (0, drizzle_orm_1.desc)(tables_1.securityConfigTable.createdAt));
    if (whereClause) {
        query.where(whereClause);
    }
    if (parsedLimit !== undefined && offset !== undefined) {
        query.limit(parsedLimit).offset(offset);
    }
    const result = await query;
    const filteredResult = result?.filter((securityConfig) => {
        const targetConfig = securityConfig?.config?.target?.config;
        if (excludeCustomProviders && targetConfig?.setupMethod && targetConfig?.setupMethod !== 'upload') {
            return false;
        }
        return true;
    });
    if (parsedLimit !== undefined && parsedPage !== undefined) {
        pagination = {
            total: totalCount,
            page: parsedPage,
            limit: parsedLimit,
            totalPages: Math.ceil(totalCount / parsedLimit),
        };
        return {
            data: filteredResult?.map((securityConfig) => ({
                id: securityConfig.id,
                name: securityConfig.name,
                status: securityConfig.status,
                appName: securityConfig?.config?.target?.label,
                strategiesCount: securityConfig?.config?.strategies?.length || 0,
                pluginsCount: securityConfig?.config?.plugins?.length || 0,
                securityEvaluationsCount: securityConfig?.securityEvaluationsCount || 0,
                is_archived: securityConfig?.is_archived || false,
            })),
            pagination,
        };
    }
    return filteredResult;
}
async function deleteSecurityConfig(id) {
    const db = await (0, database_1.getDb)();
    const result = await db
        .delete(tables_1.securityConfigTable)
        .where((0, drizzle_orm_1.eq)(tables_1.securityConfigTable.id, id))
        .execute();
    if (result.rowCount === 0) {
        throw new Error('Security config not found');
    }
    return { message: 'Security config deleted successfully' };
}
async function archiveSecurityConfig(id) {
    const db = await (0, database_1.getDb)();
    const result = await db
        .update(tables_1.securityConfigTable)
        .set({
        is_archived: true,
        updatedAt: new Date()
    })
        .where((0, drizzle_orm_1.eq)(tables_1.securityConfigTable.id, id))
        .execute();
    if (result.rowCount === 0) {
        throw new Error('Security config not found');
    }
    return { message: 'Security config archived successfully' };
}
//# sourceMappingURL=security-config.js.map