"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CustomAIApplicationService = exports.MASKED_ENV_VALUE = void 0;
const fs_1 = __importDefault(require("fs"));
const drizzle_orm_1 = require("drizzle-orm");
const database_1 = require("../../database");
const tables_1 = require("../../database/tables");
const logger_1 = __importDefault(require("../../logger"));
const types_1 = require("../../util/customAIApplication/types");
const validateCustomAIBundle_1 = require("../../util/customAIApplication/validateCustomAIBundle");
const prepareSessionBundle_1 = require("../../util/azureSessions/prepareSessionBundle");
const uploadFilesToAzure_1 = require("../../util/uploadFilesToAzure");
const SecretProviderService_1 = require("../../util/secrets/SecretProviderService");
function parseLanguage(value) {
    if (value === 'python' || value === 'js' || value === 'go') {
        return value;
    }
    return null;
}
exports.MASKED_ENV_VALUE = '***';
function parseEnvVariables(raw) {
    if (!raw) {
        return {};
    }
    let parsed = raw;
    if (typeof raw === 'string') {
        try {
            parsed = JSON.parse(raw);
        }
        catch {
            return {};
        }
    }
    if (!Array.isArray(parsed)) {
        return {};
    }
    const env = {};
    for (const row of parsed) {
        if (!row || typeof row !== 'object') {
            continue;
        }
        const key = String(row.key ?? '').trim();
        if (!key) {
            continue;
        }
        env[key] = String(row.value ?? '');
    }
    return env;
}
function getSecretProvider() {
    return new SecretProviderService_1.SecretProviderService({
        cloudProvider: process.env.CLOUD_PROVIDER ?? 'azure',
        vault: process.env.DEFAULT_AZURE_VAULT,
        region: process.env.AWS_REGION ?? process.env.DEFAULT_AZURE_REGION,
    });
}
function mergeCustomAIProviderConfig(existing, providerPatch) {
    const config = (existing.config ?? {});
    const priorProvider = (config.provider ?? {});
    const providerType = existing.provider_type;
    return {
        ...existing,
        provider_type: providerType === 'custom' ||
            providerType === 'llm' ||
            providerType === 'web_socket' ||
            providerType === 'phone' ||
            providerType === 'agentic'
            ? providerType
            : 'custom',
        config: {
            ...config,
            custom_provider_type: 'Custom AI Application',
            provider: (() => {
                const merged = { ...priorProvider, ...providerPatch };
                if ('transformResponse' in providerPatch) {
                    const trimmed = providerPatch.transformResponse?.trim();
                    if (trimmed) {
                        merged.transformResponse = trimmed;
                    }
                    else {
                        delete merged.transformResponse;
                    }
                }
                return merged;
            })(),
        },
    };
}
class CustomAIApplicationService {
    validateBundleFromPath(params) {
        const zipBuffer = fs_1.default.readFileSync(params.zipFilePath);
        return (0, validateCustomAIBundle_1.validateCustomAIBundle)({
            zipBuffer,
            language: params.language,
            applicationPath: params.applicationPath,
        });
    }
    async validateStoredBundle(params) {
        const db = await (0, database_1.getDb)();
        const [existing] = await db
            .select({ provider_config: tables_1.applicationsTable.provider_config })
            .from(tables_1.applicationsTable)
            .where((0, drizzle_orm_1.eq)(tables_1.applicationsTable.id, params.applicationId))
            .limit(1);
        if (!existing) {
            throw new Error('Application not found');
        }
        const providerConfig = (existing.provider_config ?? {});
        const config = (providerConfig.config ?? {});
        const provider = (config.provider ?? {});
        const bundleBlobUrl = typeof provider.bundleBlobUrl === 'string' ? provider.bundleBlobUrl.trim() : '';
        if (!bundleBlobUrl) {
            throw new Error('No uploaded bundle found for this application');
        }
        const zipBuffer = await (0, prepareSessionBundle_1.downloadCustomAIBundleZip)(bundleBlobUrl, params.accessToken);
        return (0, validateCustomAIBundle_1.validateCustomAIBundle)({
            zipBuffer,
            language: params.language,
            applicationPath: params.applicationPath,
        });
    }
    async getMaskedEnvVariables(applicationId) {
        const secretName = (0, types_1.getCustomAIApplicationSecretsVaultName)(applicationId);
        const secretProvider = getSecretProvider();
        const exists = await secretProvider.exist(secretName);
        if (!exists) {
            return [];
        }
        const stored = await secretProvider.getAll(secretName);
        return Object.keys(stored)
            .filter((key) => String(key).trim().length > 0)
            .sort()
            .map((key) => ({
            key,
            value: exports.MASKED_ENV_VALUE,
            isMasked: true,
        }));
    }
    /** Remove all env secrets when switching from upload mode to shell/CLI. */
    async clearEnvSecrets(applicationId) {
        const secretName = (0, types_1.getCustomAIApplicationSecretsVaultName)(applicationId);
        const secretProvider = getSecretProvider();
        if (await secretProvider.exist(secretName)) {
            await secretProvider.delete(secretName);
            logger_1.default.info(`Cleared custom AI env secrets for application ${applicationId}`);
        }
    }
    async mergeEnvVariablesForPersistence(applicationId, incoming, removedKeys = []) {
        const secretName = (0, types_1.getCustomAIApplicationSecretsVaultName)(applicationId);
        const secretProvider = getSecretProvider();
        let merged = {};
        if (await secretProvider.exist(secretName)) {
            merged = { ...(await secretProvider.getAll(secretName)) };
        }
        for (const key of removedKeys) {
            const trimmed = String(key ?? '').trim();
            if (trimmed) {
                delete merged[trimmed];
            }
        }
        for (const [key, value] of Object.entries(incoming)) {
            const trimmedKey = String(key ?? '').trim();
            if (!trimmedKey) {
                continue;
            }
            if (value === exports.MASKED_ENV_VALUE) {
                continue;
            }
            merged[trimmedKey] = String(value ?? '');
        }
        return merged;
    }
    async persistEnvSecrets(applicationId, envVariables) {
        const secretName = (0, types_1.getCustomAIApplicationSecretsVaultName)(applicationId);
        const secretProvider = getSecretProvider();
        const exists = await secretProvider.exist(secretName);
        if (Object.keys(envVariables).length === 0) {
            if (exists) {
                await secretProvider.delete(secretName);
            }
            return secretName;
        }
        if (exists) {
            await secretProvider.update(secretName, envVariables);
        }
        else {
            await secretProvider.create(secretName, envVariables);
        }
        return secretName;
    }
    async uploadBundleAndUpdateApplication(params) {
        const validation = this.validateBundleFromPath({
            zipFilePath: params.zipFilePath,
            language: params.language,
            applicationPath: params.applicationPath,
        });
        if (!validation.valid) {
            throw new Error(validation.errors.join('; '));
        }
        const folderName = `${params.orgId}/${params.projectId}/applications/${params.applicationId}/custom-ai`;
        const uploadMap = await (0, uploadFilesToAzure_1.uploadFilesToAzure)([
            {
                filePath: params.zipFilePath,
                fileName: params.zipFileName || 'bundle.zip',
                contentType: 'application/zip',
                privateContainer: true,
                generateSasToken: true,
            },
        ], params.accessToken, folderName);
        const blobUrl = uploadMap[params.zipFileName || 'bundle.zip'] || Object.values(uploadMap)[0];
        if (!blobUrl) {
            throw new Error('Failed to upload custom AI bundle to Azure');
        }
        const mergedEnv = await this.mergeEnvVariablesForPersistence(params.applicationId, params.envVariables ?? {}, params.removedEnvKeys ?? []);
        const secretsVaultName = await this.persistEnvSecrets(params.applicationId, mergedEnv);
        const providerPatch = {
            language: params.language,
            setupMethod: params.setupMethod,
            applicationPath: params.applicationPath.trim(),
            transformResponse: params.transformResponse?.trim() || undefined,
            bundleBlobUrl: blobUrl,
            bundleBlobPath: `${folderName}/${params.zipFileName || 'bundle.zip'}`,
            bundleFileName: params.zipFileName || 'bundle.zip',
            secretsVaultName: Object.keys(mergedEnv).length > 0 ? secretsVaultName : undefined,
            bundleValidatedAt: new Date().toISOString(),
        };
        const db = await (0, database_1.getDb)();
        const [existing] = await db
            .select({ provider_config: tables_1.applicationsTable.provider_config })
            .from(tables_1.applicationsTable)
            .where((0, drizzle_orm_1.eq)(tables_1.applicationsTable.id, params.applicationId))
            .limit(1);
        if (!existing) {
            throw new Error('Application not found');
        }
        const providerConfig = (existing.provider_config ?? {});
        const nextProviderConfig = mergeCustomAIProviderConfig(providerConfig, providerPatch);
        await db
            .update(tables_1.applicationsTable)
            .set({
            provider_config: nextProviderConfig,
            updatedAt: new Date(),
        })
            .where((0, drizzle_orm_1.eq)(tables_1.applicationsTable.id, params.applicationId));
        logger_1.default.info(`Stored custom AI bundle for application ${params.applicationId}`);
        return providerPatch;
    }
    async updateApplicationSettings(params) {
        const mergedEnv = await this.mergeEnvVariablesForPersistence(params.applicationId, params.envVariables ?? {}, params.removedEnvKeys ?? []);
        const secretsVaultName = await this.persistEnvSecrets(params.applicationId, mergedEnv);
        const providerPatch = {
            language: params.language,
            setupMethod: params.setupMethod,
            applicationPath: params.applicationPath.trim(),
            transformResponse: params.transformResponse?.trim() || undefined,
            secretsVaultName: Object.keys(mergedEnv).length > 0 ? secretsVaultName : undefined,
        };
        const db = await (0, database_1.getDb)();
        const [existing] = await db
            .select({ provider_config: tables_1.applicationsTable.provider_config })
            .from(tables_1.applicationsTable)
            .where((0, drizzle_orm_1.eq)(tables_1.applicationsTable.id, params.applicationId))
            .limit(1);
        if (!existing) {
            throw new Error('Application not found');
        }
        const providerConfig = (existing.provider_config ?? {});
        const nextProviderConfig = mergeCustomAIProviderConfig(providerConfig, providerPatch);
        await db
            .update(tables_1.applicationsTable)
            .set({
            provider_config: nextProviderConfig,
            updatedAt: new Date(),
        })
            .where((0, drizzle_orm_1.eq)(tables_1.applicationsTable.id, params.applicationId));
        const mergedProvider = nextProviderConfig.config
            ?.provider;
        logger_1.default.info(`Updated custom AI settings for application ${params.applicationId}`);
        return mergedProvider ?? providerPatch;
    }
    static parseRemovedEnvKeys(raw) {
        if (!raw) {
            return [];
        }
        let parsed = raw;
        if (typeof raw === 'string') {
            try {
                parsed = JSON.parse(raw);
            }
            catch {
                return [];
            }
        }
        if (!Array.isArray(parsed)) {
            return [];
        }
        return parsed
            .map((item) => String(item ?? '').trim())
            .filter((key) => key.length > 0);
    }
    static parseRequestFields(body) {
        const language = parseLanguage(body.language);
        const applicationPath = typeof body.applicationPath === 'string' ? body.applicationPath.trim() : '';
        const envVariables = parseEnvVariables(body.envVariables);
        const removedEnvKeys = CustomAIApplicationService.parseRemovedEnvKeys(body.removedEnvKeys);
        const transformResponse = typeof body.transformResponse === 'string' ? body.transformResponse : undefined;
        const orgId = typeof body.orgId === 'string' ? body.orgId.trim() : '';
        const projectId = typeof body.projectId === 'string' ? body.projectId.trim() : '';
        return {
            language,
            applicationPath,
            envVariables,
            removedEnvKeys,
            transformResponse,
            orgId,
            projectId,
        };
    }
}
exports.CustomAIApplicationService = CustomAIApplicationService;
//# sourceMappingURL=customAIApplicationService.js.map