"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.metadata = void 0;
const db_1 = require("@b/db");
const error_1 = require("@b/utils/error");
const crypto_1 = __importDefault(require("crypto"));
const promises_1 = __importDefault(require("fs/promises"));
const path_1 = __importDefault(require("path"));
const redis_1 = require("@b/utils/redis");
const console_1 = require("@b/utils/console");
exports.metadata = {
    summary: "Verify KYC Application",
    description: "Submits a KYC application for verification using the specified verification service.",
    operationId: "verifyKycApplication",
    tags: ["KYC", "Verification Services", "Applications"],
    logModule: "ADMIN_CRM",
    logTitle: "Verify KYC application",
    parameters: [
        {
            index: 0,
            name: "id",
            in: "path",
            description: "Verification service ID",
            required: true,
            schema: { type: "string" },
        },
    ],
    requestBody: {
        required: true,
        content: {
            "application/json": {
                schema: {
                    type: "object",
                    properties: {
                        applicationId: {
                            type: "string",
                            description: "ID of the KYC application to verify",
                        },
                    },
                    required: ["applicationId"],
                },
            },
        },
    },
    responses: {
        200: {
            description: "Verification process initiated successfully.",
            content: {
                "application/json": {
                    schema: {
                        type: "object",
                        properties: {
                            id: { type: "string", description: "Verification result ID" },
                            applicationId: {
                                type: "string",
                                description: "KYC application ID",
                            },
                            serviceId: {
                                type: "string",
                                description: "Verification service ID",
                            },
                            serviceName: {
                                type: "string",
                                description: "Verification service name",
                            },
                            status: {
                                type: "string",
                                enum: ["VERIFIED", "FAILED", "PENDING", "NOT_STARTED"],
                                description: "Verification status",
                            },
                            score: {
                                type: "number",
                                description: "Verification confidence score (percentage)",
                                nullable: true,
                            },
                            checks: {
                                type: "object",
                                description: "Structured verification checks (JSON object with keys such as summary, selfieMatch, documentAuthentic, confidenceScore, issues, and extractedInfo)",
                                nullable: true,
                            },
                            documentVerifications: {
                                type: "object",
                                description: "Document verification details including a human‐readable summary (aiResponse)",
                                nullable: true,
                            },
                            createdAt: {
                                type: "string",
                                format: "date-time",
                                description: "When the verification was initiated",
                            },
                        },
                    },
                },
            },
        },
        400: { description: "Missing required fields." },
        404: { description: "Verification service or application not found." },
        500: { description: "Internal Server Error." },
    },
    permission: "edit.kyc.verification",
    requiresAuth: true,
};
exports.default = async (data) => {
    const { params, body, ctx } = data;
    try {
        const { id } = params;
        const { applicationId } = body;
        if (!applicationId) {
            throw (0, error_1.createError)({
                statusCode: 400,
                message: "Missing required field: applicationId",
            });
        }
        ctx === null || ctx === void 0 ? void 0 : ctx.step(`Fetching KYC application ${applicationId}`);
        const application = await db_1.models.kycApplication.findByPk(applicationId, {
            include: [
                {
                    model: db_1.models.kycLevel,
                    as: "level",
                    paranoid: false,
                    include: [
                        {
                            model: db_1.models.kycVerificationService,
                            as: "verificationService",
                        },
                    ],
                },
            ],
        });
        if (!application) {
            throw (0, error_1.createError)({
                statusCode: 404,
                message: "KYC application not found",
            });
        }
        const level = application.level;
        if (!level) {
            throw (0, error_1.createError)({
                statusCode: 404,
                message: "KYC level not found for this application",
            });
        }
        const service = level.verificationService;
        if (!service) {
            throw (0, error_1.createError)({
                statusCode: 404,
                message: "Verification service not found in KYC level configuration",
            });
        }
        ctx === null || ctx === void 0 ? void 0 : ctx.step(`Initiating verification with ${service.type}`);
        let verificationResponse;
        if (service.type === "SUMSUB") {
            verificationResponse = await verifyWithSumSub(application, level);
        }
        else if (service.type === "GEMINI") {
            verificationResponse = await verifyWithGemini(application, level);
        }
        else {
            throw (0, error_1.createError)({
                statusCode: 400,
                message: `Unsupported verification service type: ${service.type}`,
            });
        }
        ctx === null || ctx === void 0 ? void 0 : ctx.step("Saving verification result");
        const verificationResult = await db_1.models.kycVerificationResult.create({
            applicationId,
            serviceId: service.id,
            status: verificationResponse.status,
            score: verificationResponse.score,
            checks: verificationResponse.checks,
            documentVerifications: verificationResponse.documentVerifications,
            createdAt: new Date(),
        });
        const newApplicationStatus = verificationResponse.status === "PENDING"
            ? "PENDING"
            : verificationResponse.status === "VERIFIED"
                ? "APPROVED"
                : verificationResponse.status === "FAILED"
                    ? "REJECTED"
                    : "PENDING";
        ctx === null || ctx === void 0 ? void 0 : ctx.step(`Updating application status to ${newApplicationStatus}`);
        await application.update({
            status: newApplicationStatus,
            updatedAt: new Date(),
            reviewedAt: new Date(),
        });
        if (newApplicationStatus === "APPROVED") {
            try {
                const redis = redis_1.RedisSingleton.getInstance();
                await redis.del(`user:${application.userId}:profile`);
            }
            catch (error) {
                console_1.logger.error("KYC", "Error clearing user cache after KYC approval", error);
            }
        }
        ctx === null || ctx === void 0 ? void 0 : ctx.success(`Verification completed with status: ${verificationResponse.status}`);
        return {
            id: verificationResult.id,
            applicationId,
            serviceId: service.id,
            serviceName: service.name,
            status: verificationResponse.status,
            score: verificationResponse.score,
            checks: verificationResponse.checks,
            documentVerifications: verificationResponse.documentVerifications,
            createdAt: verificationResult.createdAt,
        };
    }
    catch (error) {
        console_1.logger.error("KYC", "Error in verifyKycApplication", error);
        if (error.response) {
            console_1.logger.error("KYC", `API Response Error: status=${error.response.status}, data=${JSON.stringify(error.response.data)}`);
        }
        throw (0, error_1.createError)({
            statusCode: error.statusCode || 500,
            message: error.message || "Failed to initiate verification process",
        });
    }
};
async function verifyWithGemini(application, level) {
    var _a;
    try {
        const apiKey = process.env.GEMINI_API_KEY;
        if (!apiKey) {
            throw new Error("Gemini API key not configured");
        }
        let submittedData = application.submittedData || application.data;
        if (!submittedData) {
            throw new Error("No submitted data found in application");
        }
        if (typeof submittedData === "string") {
            submittedData = JSON.parse(submittedData);
        }
        const fields = typeof level.fields === "string"
            ? JSON.parse(level.fields)
            : level.fields;
        const processedData = processLevelFields(fields, submittedData);
        if (Object.keys(processedData.documents).length === 0) {
            throw new Error("No documents found for verification");
        }
        const { GoogleGenerativeAI } = await Promise.resolve().then(() => __importStar(require("@google/generative-ai")));
        const genAI = new GoogleGenerativeAI(apiKey);
        const model = genAI.getGenerativeModel({ model: "gemini-1.5-pro" });
        const parts = [];
        const verificationPrompt = createDynamicVerificationPrompt(processedData);
        parts.push(verificationPrompt);
        for (const [docKey, docUrl] of Object.entries(processedData.documents)) {
            try {
                const fieldDef = findFieldById(fields, docKey.split("-")[0]);
                const fieldLabel = fieldDef ? fieldDef.label : docKey;
                if (String(docUrl).startsWith("http")) {
                    parts.push({
                        fileData: {
                            fileUri: String(docUrl),
                            mimeType: "image/jpeg",
                        },
                    });
                }
                else {
                    const docBase64 = await convertImageToBase64(String(docUrl));
                    parts.push({
                        inlineData: {
                            data: docBase64,
                            mimeType: "image/jpeg",
                        },
                    });
                }
                parts.push(`This is the ${fieldLabel}.`);
            }
            catch (error) {
                console_1.logger.warn("KYC", `Failed to process document ${docKey}: ${error}`);
            }
        }
        const generatedContent = await model.generateContent(parts, {
            responseFormat: { type: "json_object" },
        });
        const aiResponseRaw = generatedContent.response.text();
        const aiResponseClean = cleanJSONOutput(aiResponseRaw);
        let parsedResult = null;
        try {
            parsedResult = JSON.parse(aiResponseClean);
        }
        catch (e) {
            console_1.logger.warn("KYC", "Failed to parse Gemini response as JSON, falling back to basic analysis");
        }
        const finalResult = parsedResult || analyzeAIResponse(aiResponseClean);
        const overallStatus = finalResult.selfieMatch === true && finalResult.documentAuthentic === true
            ? "VERIFIED"
            : "FAILED";
        const rawConfidence = (_a = finalResult.confidenceScore) !== null && _a !== void 0 ? _a : 0.6;
        const confidenceScore = rawConfidence <= 1 ? rawConfidence * 100 : rawConfidence;
        const documentVerifications = {
            message: "Gemini verification completed",
            details: "Document verification has been completed by Gemini AI.",
            aiResponse: finalResult.summary ? finalResult.summary : aiResponseClean,
        };
        return {
            status: overallStatus,
            score: confidenceScore,
            checks: finalResult,
            documentVerifications,
        };
    }
    catch (error) {
        console_1.logger.error("KYC", "Gemini verification error", error);
        let errorMessage = "An error occurred during Gemini verification";
        const errorDetails = error.message || "";
        if (error.status === 400) {
            errorMessage = "Invalid request format";
        }
        else if (error.status === 401) {
            errorMessage = "Authentication failed - invalid API key";
        }
        else if (error.status === 402) {
            errorMessage = "Insufficient balance in Gemini account";
        }
        else if (error.status === 422) {
            errorMessage = "Invalid parameters in request";
        }
        else if (error.status === 429) {
            errorMessage = "Rate limit reached - too many requests";
        }
        else if (error.status === 500) {
            errorMessage = "Gemini server error";
        }
        else if (error.status === 503) {
            errorMessage = "Gemini server overloaded";
        }
        return {
            status: "FAILED",
            score: null,
            checks: {
                message: "Gemini verification failed",
                details: errorMessage,
                error: errorDetails,
            },
            documentVerifications: null,
        };
    }
}
function cleanJSONOutput(text) {
    return text.replace(/```(json)?/g, "").trim();
}
async function convertImageToBase64(imageUrl) {
    if (imageUrl.startsWith("data:image")) {
        return imageUrl.split(",")[1];
    }
    if (!imageUrl.startsWith("http")) {
        const BASE_UPLOAD_DIR = path_1.default.join(process.cwd(), "..", "frontend", "public");
        const filePath = path_1.default.join(BASE_UPLOAD_DIR, imageUrl);
        try {
            const data = await promises_1.default.readFile(filePath);
            return data.toString("base64");
        }
        catch (error) {
            console_1.logger.error("KYC", "Error reading local file", error);
            throw new Error("Failed to process local image for verification");
        }
    }
    try {
        const response = await fetch(imageUrl);
        const arrayBuffer = await response.arrayBuffer();
        return Buffer.from(arrayBuffer).toString("base64");
    }
    catch (error) {
        console_1.logger.error("KYC", "Error converting image to base64", error);
        throw new Error("Failed to process image for verification");
    }
}
function processLevelFields(fields, submittedData) {
    const result = {
        textFields: {},
        documents: {},
        personalInfo: {},
        addresses: [],
        hasSelfie: false,
    };
    processFields(fields, submittedData, result);
    return result;
}
function processFields(fields, submittedData, result, parentPath = "") {
    fields.forEach((field) => {
        const fieldId = field.id;
        const fieldValue = submittedData[fieldId];
        if (fieldValue === undefined || fieldValue === null)
            return;
        const fieldType = field.type;
        const fieldLabel = field.label ? field.label.toLowerCase() : "";
        if (fieldType === "FILE" || fieldType === "IMAGE") {
            result.documents[fieldId] = fieldValue;
            if (fieldLabel.includes("selfie")) {
                result.hasSelfie = true;
            }
        }
        else if (fieldType === "IDENTITY") {
            if (typeof fieldValue === "object") {
                for (const key in fieldValue) {
                    if (key === "type")
                        continue;
                    if (typeof fieldValue[key] === "string" &&
                        fieldValue[key].trim() !== "") {
                        const compositeKey = `${fieldId}-${key}`;
                        result.documents[compositeKey] = fieldValue[key];
                        if (key.toLowerCase().includes("selfie")) {
                            result.hasSelfie = true;
                        }
                    }
                }
            }
        }
        else if (fieldType === "SECTION" || fieldType === "SUBSECTION") {
            if (field.fields && Array.isArray(field.fields)) {
                processFields(field.fields, submittedData, result, `${parentPath}${fieldId}.`);
            }
        }
        else {
            result.textFields[fieldId] = {
                value: fieldValue,
                label: field.label,
                type: fieldType,
            };
            if (fieldType === "TEXT") {
                if (fieldLabel.includes("first name")) {
                    result.personalInfo.firstName = fieldValue;
                }
                else if (fieldLabel.includes("last name")) {
                    result.personalInfo.lastName = fieldValue;
                }
                else if (fieldLabel.includes("nationality")) {
                    result.personalInfo.nationality = fieldValue;
                }
            }
            else if (fieldType === "EMAIL") {
                result.personalInfo.email = fieldValue;
            }
            else if (fieldType === "PHONE") {
                result.personalInfo.phone = fieldValue;
            }
            else if (fieldType === "DATE" && fieldLabel.includes("birth")) {
                result.personalInfo.dob = fieldValue;
            }
            else if (fieldType === "SELECT" && fieldLabel.includes("country")) {
                result.personalInfo.country = fieldValue;
            }
            else if (fieldType === "ADDRESS" && typeof fieldValue === "object") {
                result.addresses.push({
                    country: fieldValue.country,
                    postCode: fieldValue.postalCode,
                    town: fieldValue.city,
                    street: fieldValue.street,
                    subStreet: fieldValue.apartment,
                    state: fieldValue.state,
                });
            }
        }
    });
}
function findFieldById(fields, fieldId, parentPath = "") {
    for (const field of fields) {
        if (field.id === fieldId)
            return field;
        if ((field.type === "SECTION" || field.type === "SUBSECTION") &&
            field.fields &&
            Array.isArray(field.fields)) {
            const nestedField = findFieldById(field.fields, fieldId, `${parentPath}${field.id}.`);
            if (nestedField)
                return nestedField;
        }
    }
    return null;
}
function createDynamicVerificationPrompt(processedData) {
    const prompt = `
Please verify the following ID document(s) for authenticity and confirm whether the provided personal information matches the document. Use the data below to assess both the document and the selfie (if provided).

User Information:
${JSON.stringify(processedData.personalInfo, null, 2)}

Document Data:
${JSON.stringify(processedData.documents, null, 2)}

Verification Tasks:
1. Check if the document appears authentic.
2. Verify that the user information matches what is on the document.
3. If a selfie is provided, determine whether the selfie image matches the photo on the document.
4. Provide a human-readable summary of the verification outcome.

Please output your answer as a raw JSON object (without any markdown or code block formatting) with exactly the following keys:
{
  "summary": string,
  "selfieMatch": boolean,
  "documentAuthentic": boolean,
  "confidenceScore": number,
  "issues": string[],
  "extractedInfo": {}
}
`;
    return prompt;
}
function analyzeAIResponse(aiResponse) {
    const lower = aiResponse.toLowerCase();
    const isDocumentValid = !lower.includes("fake") &&
        !lower.includes("tampered") &&
        !lower.includes("manipulated") &&
        !lower.includes("fail");
    let confidenceScore = 60;
    const confidenceMatch = aiResponse.match(/confidence(?:\s+score)?(?:\s+of)?(?:\s+is)?[:\s]+(\d+)%?/i);
    if (confidenceMatch && confidenceMatch[1]) {
        confidenceScore = Number.parseInt(confidenceMatch[1], 10);
    }
    else {
        confidenceScore = lower.includes("high confidence")
            ? 90
            : lower.includes("medium confidence")
                ? 70
                : lower.includes("low confidence")
                    ? 40
                    : 60;
    }
    const detectedIssues = [];
    if (lower.includes("mismatch"))
        detectedIssues.push("Information mismatch");
    if (lower.includes("blur"))
        detectedIssues.push("Blurry image");
    if (lower.includes("expired"))
        detectedIssues.push("Expired document");
    if (lower.includes("damaged"))
        detectedIssues.push("Damaged document");
    if (lower.includes("photoshop"))
        detectedIssues.push("Signs of manipulation");
    if (lower.includes("selfie") &&
        lower.includes("match") &&
        lower.includes("not"))
        detectedIssues.push("Selfie does not match ID photo");
    const summary = aiResponse.split("\n\n")[0] || aiResponse.split(".")[0] + ".";
    const extractedInfo = {};
    const nameMatch = aiResponse.match(/name(?:\s+is)?[:\s]+([A-Za-z\s]+)/i);
    if (nameMatch && nameMatch[1]) {
        extractedInfo.name = nameMatch[1].trim();
    }
    const dobMatch = aiResponse.match(/(?:date of birth|dob|born)(?:\s+is)?[:\s]+([A-Za-z0-9\s,./-]+)/i);
    if (dobMatch && dobMatch[1]) {
        extractedInfo.dateOfBirth = dobMatch[1].trim();
    }
    const docNumberMatch = aiResponse.match(/(?:document|id|passport)(?:\s+number)(?:\s+is)?[:\s]+([A-Za-z0-9\s]+)/i);
    if (docNumberMatch && docNumberMatch[1]) {
        extractedInfo.documentNumber = docNumberMatch[1].trim();
    }
    return {
        summary,
        selfieMatch: lower.includes("selfie match") && !lower.includes("not"),
        documentAuthentic: isDocumentValid,
        confidenceScore,
        issues: detectedIssues,
        extractedInfo,
    };
}
async function verifyWithSumSub(application, level) {
    try {
        const apiKey = process.env.SUMSUB_API_KEY;
        const apiSecret = process.env.SUMSUB_API_SECRET;
        if (!apiKey || !apiSecret) {
            throw new Error("SumSub API credentials not configured");
        }
        const { userId, submittedData } = application;
        if (!submittedData) {
            throw new Error("No submitted data found in application");
        }
        const processedData = processLevelFields(level.fields, submittedData);
        const ts = Math.floor(Date.now() / 1000).toString();
        const requestBody = JSON.stringify({
            externalUserId: userId,
            info: {
                ...processedData.personalInfo,
                addresses: processedData.addresses.length > 0
                    ? processedData.addresses
                    : undefined,
            },
            requiredIdDocs: {
                docSets: [
                    {
                        idDocSetType: "IDENTITY",
                        types: ["PASSPORT", "ID_CARD", "DRIVERS"],
                        subTypes: ["FRONT_SIDE", "BACK_SIDE"],
                    },
                    ...(processedData.hasSelfie
                        ? [
                            {
                                idDocSetType: "SELFIE",
                                types: ["SELFIE"],
                                subTypes: [],
                            },
                        ]
                        : []),
                ],
            },
        });
        const signature = crypto_1.default
            .createHmac("sha256", apiSecret)
            .update(ts + requestBody)
            .digest("hex");
        const response = await fetch("https://api.sumsub.com/resources/applicants", {
            method: "POST",
            headers: {
                "X-App-Token": apiKey,
                "X-App-Access-Sig": signature,
                "X-App-Access-Ts": ts,
                "Content-Type": "application/json",
            },
            body: requestBody,
        });
        if (!response.ok) {
            const errorData = (await response.json());
            throw new Error(`SumSub API error: ${errorData.description || response.statusText}`);
        }
        const responseData = (await response.json());
        return {
            status: "PENDING",
            score: null,
            checks: {
                message: "SumSub verification initiated",
                details: "Your application has been sent to SumSub for verification.",
                applicantId: responseData.id,
                reviewStatus: responseData.reviewStatus,
                externalUserId: responseData.externalUserId,
                processedFields: Object.keys(submittedData).length,
            },
            documentVerifications: {
                message: "SumSub verification completed",
                details: "No further document analysis available for SumSub.",
            },
        };
    }
    catch (error) {
        console_1.logger.error("KYC", "SumSub verification error", error);
        return {
            status: "FAILED",
            score: null,
            checks: {
                message: "SumSub verification failed",
                details: error.message || "An error occurred during SumSub verification",
                error: error.toString(),
            },
            documentVerifications: null,
        };
    }
}
