"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
var KycVerificationService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.KycVerificationService = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const user_entity_1 = require("../user/user.entity");
const user_services_1 = require("../user/user.services");
const kyc_verification_attempt_entity_1 = require("../../common/kyc/kyc-verification-attempt.entity");
const kyc_status_enum_1 = require("../../common/kyc/kyc-status.enum");
const didit_service_1 = require("./didit.service");
const firebase_service_1 = require("../fcm/firebase.service");
const user_entity_2 = require("../user/user.entity");
const vendor_plan_order_entity_1 = require("../../common/vendor/vendor_plan_order.entity");
const vendor_entity_1 = require("../../common/vendor/vendor.entity");
const vendor_plan_entity_1 = require("../../common/vendor/vendor_plan.entity");
const status_enum_1 = require("../../common/columntype/status.enum");
const otp_email_service_1 = require("../emailotp/otp-email.service");
let KycVerificationService = KycVerificationService_1 = class KycVerificationService {
    constructor(diditService, userService, firebaseService, emailService, attemptRepository, userRepository, orderRepo, vendorRepo, vendorPlanRepo) {
        this.diditService = diditService;
        this.userService = userService;
        this.firebaseService = firebaseService;
        this.emailService = emailService;
        this.attemptRepository = attemptRepository;
        this.userRepository = userRepository;
        this.orderRepo = orderRepo;
        this.vendorRepo = vendorRepo;
        this.vendorPlanRepo = vendorPlanRepo;
        this.logger = new common_1.Logger(KycVerificationService_1.name);
    }
    async createSession(userId, dto) {
        const workflowId = dto.workflow_id || this.diditService.getDefaultKycWorkflowId();
        if (!workflowId) {
            throw new common_1.BadRequestException("Missing workflow_id (pass in body or set DIDIT_KYC_WORKFLOW_ID in env)");
        }
        const vendorData = dto.vendor_data || String(userId);
        const callbackUrl = this.diditService.getDefaultKycCallbackUrl() || dto.callback;
        const payload = {
            ...dto,
            workflow_id: workflowId,
            vendor_data: vendorData,
            ...(callbackUrl ? { callback: callbackUrl } : {}),
            metadata: this.normalizeMetadata({
                ...(typeof dto.metadata === "object" && dto.metadata !== null ? dto.metadata : {}),
                listingType: dto.listingType || null,
                userId,
            }),
        };
        delete payload.listingType;
        const session = await this.diditService.createSession(payload);
        const providerSessionId = this.extractProviderSessionId(session);
        const attempt = this.attemptRepository.create({
            userId,
            provider: "didit",
            providerSessionId,
            workflowId,
            status: kyc_status_enum_1.KycVerificationStatus.PENDING,
            metadata: payload.metadata,
            providerPayload: session,
            decisionSource: "api_session",
        });
        const savedAttempt = await this.attemptRepository.save(attempt);
        await this.userService.updateUserKycState(userId, kyc_status_enum_1.KycVerificationStatus.PENDING);
        return {
            ...session,
            providerSessionId,
            attemptId: savedAttempt.id,
            kycStatus: kyc_status_enum_1.KycVerificationStatus.PENDING,
        };
    }
    async getUserStatus(userId) {
        const user = await this.userRepository.findOne({ where: { id: userId } });
        if (!user) {
            throw new common_1.NotFoundException(`User with id ${userId} not found`);
        }
        const latestAttempt = await this.attemptRepository.findOne({
            where: { userId },
            order: { createdAt: "DESC" },
        });
        return {
            userId: user.id,
            verified: user.userKycVerified,
            status: user.kycStatus,
            rejectionReason: user.kycRejectedReason,
            verifiedAt: user.kycLastVerifiedAt,
            latestAttempt,
            canRetry: [
                kyc_status_enum_1.KycVerificationStatus.UNVERIFIED,
                kyc_status_enum_1.KycVerificationStatus.REJECTED,
                kyc_status_enum_1.KycVerificationStatus.NEEDS_REVIEW,
            ].includes(user.kycStatus),
        };
    }
    async getUserHistory(userId) {
        return this.attemptRepository.find({
            where: { userId },
            order: { createdAt: "DESC" },
        });
    }
    async getAttemptDiditDetails(userId) {
        const attempts = await this.attemptRepository.find({
            where: { userId },
            order: { createdAt: "DESC" },
        });
        const attempt = attempts.find((a) => a.providerSessionId && a.provider === "didit")
            || attempts.find((a) => a.providerSessionId)
            || null;
        if (!attempt?.providerSessionId) {
            return { hasSession: false };
        }
        let liveData = null;
        try {
            liveData = await this.diditService.getSession(attempt.providerSessionId);
        }
        catch {
        }
        const storedPayload = attempt.providerPayload || null;
        const sessionData = liveData || storedPayload;
        let documents = this.extractDocumentImages(sessionData);
        let liveness = this.extractLivenessPhoto(sessionData);
        const extractedData = this.extractDocumentData(sessionData);
        const mediaBase = (process.env.DIDIT_MEDIA_BASE_URL || 'https://apx.didit.me').replace(/\/$/, '');
        const resolveMediaPath = (path) => {
            if (!path)
                return null;
            if (path.startsWith('http://') || path.startsWith('https://'))
                return path;
            return `${mediaBase}/${path}`;
        };
        let portraitPath = null;
        let frontPath = null;
        let backPath = null;
        let selfiePath = null;
        for (const a of attempts) {
            const changes = a.providerPayload?.changes || {};
            if (!portraitPath && changes.portrait_image?.current)
                portraitPath = changes.portrait_image.current;
            if (!frontPath && changes.document_front?.current)
                frontPath = changes.document_front.current;
            if (!backPath && changes.document_back?.current)
                backPath = changes.document_back.current;
            if (!selfiePath && (changes.selfie?.current || changes.face_image?.current)) {
                selfiePath = changes.selfie?.current || changes.face_image?.current;
            }
        }
        const mergedDoc = {
            frontUrl: resolveMediaPath(documents?.frontUrl ?? frontPath),
            backUrl: resolveMediaPath(documents?.backUrl ?? backPath),
            documentPhotoUrl: resolveMediaPath(documents?.documentPhotoUrl ?? portraitPath),
            documentType: documents?.documentType ?? null,
            country: documents?.country ?? null,
            status: documents?.status ?? null,
        };
        const mergedLiveness = {
            selfieUrl: resolveMediaPath(liveness?.selfieUrl ?? selfiePath),
            similarityScore: liveness?.similarityScore ?? null,
            livenessStatus: liveness?.livenessStatus ?? null,
        };
        documents = mergedDoc;
        liveness = mergedLiveness;
        let diditStatus = null;
        for (const a of attempts) {
            if (a.providerPayload?.status) {
                diditStatus = a.providerPayload.status;
                break;
            }
        }
        if (!diditStatus) {
            diditStatus = sessionData?.status || null;
        }
        return {
            hasSession: true,
            sessionId: attempt.providerSessionId,
            attemptId: attempt.id,
            internalStatus: attempt.status,
            decisionSource: attempt.decisionSource,
            diditStatus,
            mediaBaseUrl: mediaBase,
            documents,
            liveness,
            extractedData,
        };
    }
    resolveKycBlock(session) {
        return (session?.features?.kyc ||
            session?.decision?.kyc ||
            session?.kyc ||
            null);
    }
    extractDocumentImages(session) {
        const idV = session?.decision?.id_verifications?.[0] || null;
        if (idV) {
            return {
                frontUrl: idV.front_image || idV.full_front_image || null,
                backUrl: idV.back_image || idV.full_back_image || null,
                documentPhotoUrl: idV.portrait_image || null,
                documentType: idV.document_type || null,
                country: idV.issuing_state_name || idV.issuing_state || idV.nationality || null,
                status: idV.status || null,
            };
        }
        const kyc = this.resolveKycBlock(session);
        const doc = kyc?.document || kyc?.id_verification || session?.document || null;
        if (!doc)
            return null;
        const images = doc.images || doc;
        return {
            frontUrl: images.front || images.front_url || images.front_photo_url || doc.front_url || null,
            backUrl: images.back || images.back_url || images.back_photo_url || doc.back_url || null,
            documentPhotoUrl: images.portrait || images.portrait_url || doc.portrait_url || null,
            documentType: doc.document_type || doc.type || null,
            country: doc.country || doc.issuing_country || null,
            status: doc.status || null,
        };
    }
    extractLivenessPhoto(session) {
        const faceMatch = session?.decision?.face_matches?.[0] || null;
        if (faceMatch) {
            return {
                selfieUrl: faceMatch.source_image || null,
                similarityScore: faceMatch.score != null ? faceMatch.score / 100 : null,
                livenessStatus: faceMatch.status || null,
            };
        }
        const kyc = this.resolveKycBlock(session);
        const face = kyc?.face_authentication || kyc?.face || null;
        const liveness = kyc?.liveness || kyc?.selfie || session?.liveness || null;
        return {
            selfieUrl: liveness?.image || face?.image || face?.selfie_url || null,
            similarityScore: face?.similarity_score ?? null,
            livenessStatus: liveness?.status || face?.status || null,
        };
    }
    extractDocumentData(session) {
        const idV = session?.decision?.id_verifications?.[0] || null;
        if (idV) {
            return {
                firstName: idV.first_name || null,
                lastName: idV.last_name || null,
                dateOfBirth: idV.date_of_birth || null,
                nationality: idV.nationality || null,
                documentNumber: idV.document_number || idV.personal_number || null,
                expiryDate: idV.expiration_date || idV.expiry_date || null,
            };
        }
        const kyc = this.resolveKycBlock(session);
        const doc = kyc?.document || kyc?.id_verification || session?.document || null;
        if (!doc)
            return null;
        const fields = doc.extracted_fields || doc.fields || doc;
        return {
            firstName: fields.first_name || doc.first_name || null,
            lastName: fields.last_name || doc.last_name || null,
            dateOfBirth: fields.date_of_birth || doc.date_of_birth || null,
            nationality: fields.nationality || doc.nationality || null,
            documentNumber: fields.document_number || doc.document_number || null,
            expiryDate: fields.expiry_date || doc.expiry_date || null,
        };
    }
    get isAutoApproveEnabled() {
        return (process.env.KYC_AUTO_APPROVE ?? "false").toLowerCase() === "true";
    }
    async handleDiditWebhookPayload(body) {
        const providerSessionId = String(body?.session_id || "").trim() || null;
        const rawStatus = this.extractProviderStatus(body);
        const webhookType = body?.webhook_type || "";
        const changes = body?.changes || {};
        const approvedCount = changes?.approved_count?.current ?? null;
        const inReviewCount = changes?.in_review_count?.current ?? null;
        const normalizedStatus = String(rawStatus).trim().toLowerCase();
        const isDiditApproved = normalizedStatus === "approved" ||
            normalizedStatus === "active" ||
            (webhookType === "user.data.updated" &&
                approvedCount !== null &&
                inReviewCount !== null &&
                approvedCount > 0 &&
                inReviewCount === 0);
        let mappedStatus;
        if (isDiditApproved) {
            mappedStatus = this.isAutoApproveEnabled
                ? kyc_status_enum_1.KycVerificationStatus.VERIFIED
                : kyc_status_enum_1.KycVerificationStatus.NEEDS_REVIEW;
        }
        else if (webhookType === "user.data.updated") {
            mappedStatus = kyc_status_enum_1.KycVerificationStatus.PENDING;
        }
        else {
            mappedStatus = this.mapProviderStatus(rawStatus);
        }
        const rejectionReason = this.extractRejectionReason(body);
        let attempt = providerSessionId
            ? await this.attemptRepository.findOne({ where: { providerSessionId } })
            : null;
        const candidateUserId = Number(body?.vendor_data);
        const userId = attempt?.userId || (Number.isFinite(candidateUserId) ? candidateUserId : null);
        if (!attempt && userId && webhookType === "user.data.updated") {
            attempt = await this.attemptRepository.findOne({
                where: { userId, provider: "didit" },
                order: { createdAt: "DESC" },
            }) ?? undefined;
        }
        if (!attempt && userId) {
            attempt = this.attemptRepository.create({
                userId,
                provider: "didit",
                providerSessionId,
                workflowId: body?.workflow_id || null,
                status: mappedStatus,
                rejectionReason,
                providerPayload: body,
                decisionSource: "webhook",
                completedAt: this.isFinalStatus(mappedStatus) ? new Date() : null,
            });
        }
        else if (attempt) {
            attempt.status = mappedStatus;
            attempt.rejectionReason = rejectionReason;
            attempt.decisionSource = "webhook";
            attempt.completedAt = this.isFinalStatus(mappedStatus) ? new Date() : attempt.completedAt;
            if (webhookType === "user.data.updated" && attempt.providerPayload) {
                const existingChanges = attempt.providerPayload.changes || {};
                attempt.providerPayload = {
                    ...attempt.providerPayload,
                    changes: { ...existingChanges, ...(body.changes || {}) },
                    status: body.status,
                    timestamp: body.timestamp,
                };
            }
            else {
                attempt.providerPayload = body;
            }
        }
        if (attempt) {
            await this.attemptRepository.save(attempt);
        }
        if (userId) {
            const user = await this.userService.updateUserKycState(userId, mappedStatus, rejectionReason);
            await this.sendKycStatusNotification(user, mappedStatus, rejectionReason);
        }
        return {
            providerSessionId,
            userId,
            mappedStatus,
            rejectionReason,
        };
    }
    async adminListUsers(query) {
        const qb = this.userRepository.createQueryBuilder("user").orderBy("user.updatedAt", "DESC");
        if (query.status) {
            qb.andWhere("user.kycStatus = :status", { status: query.status });
        }
        if (query.email) {
            qb.andWhere("LOWER(user.email) LIKE :email", {
                email: `%${query.email.trim().toLowerCase()}%`,
            });
        }
        if (query.mobileNumber) {
            qb.andWhere("user.mobileNumber LIKE :mobileNumber", {
                mobileNumber: `%${query.mobileNumber.trim()}%`,
            });
        }
        const numericUserId = Number(query.userId);
        if (Number.isFinite(numericUserId)) {
            qb.andWhere("user.id = :userId", { userId: numericUserId });
        }
        const users = await qb.getMany();
        const userIds = users.map((user) => user.id);
        const attempts = userIds.length
            ? await this.attemptRepository
                .createQueryBuilder("attempt")
                .where("attempt.userId IN (:...userIds)", { userIds })
                .orderBy("attempt.createdAt", "DESC")
                .getMany()
            : [];
        const latestAttempts = new Map();
        for (const attempt of attempts) {
            if (!latestAttempts.has(attempt.userId)) {
                latestAttempts.set(attempt.userId, attempt);
            }
        }
        return users.map((user) => ({
            ...user,
            latestAttempt: latestAttempts.get(user.id) || null,
        }));
    }
    async adminUpdateUserStatus(userId, dto, adminUserId) {
        if (!dto.status) {
            throw new common_1.BadRequestException("status is required.");
        }
        const user = await this.userService.updateUserKycState(userId, dto.status, dto.rejectionReason ?? null);
        if (dto.status === kyc_status_enum_1.KycVerificationStatus.VERIFIED) {
            await this.userService.addRole(userId, user_entity_2.UserRole.VENDOR);
            const order = await this.orderRepo.findOne({
                where: { userId, paymentStatus: 'PENDING' },
                order: { purchaseDate: 'DESC' },
            });
            if (order) {
                order.paymentStatus = 'PAID';
                await this.orderRepo.save(order);
                await this.vendorRepo.save({
                    planId: order.planId,
                    user: { id: order.userId },
                });
                await this.vendorPlanRepo.save({
                    vendorId: order.userId,
                    planId: order.planId,
                    status: status_enum_1.Status.ACTIVE,
                    start_date: new Date(),
                });
            }
            else {
                this.logger.warn(`Admin approved KYC for user ${userId} but no pending VendorPlanOrder found — vendor role assigned but no plan activated.`);
            }
        }
        const attempt = this.attemptRepository.create({
            userId,
            provider: "manual",
            providerSessionId: `manual-${userId}-${Date.now()}`,
            status: dto.status,
            rejectionReason: dto.rejectionReason ?? null,
            reviewNotes: dto.reviewNotes ?? null,
            decisionSource: adminUserId ? `admin:${adminUserId}` : "admin",
            completedAt: this.isFinalStatus(dto.status) ? new Date() : null,
            metadata: {
                manual: true,
            },
        });
        const savedAttempt = await this.attemptRepository.save(attempt);
        await this.sendKycStatusNotification(user, dto.status, dto.rejectionReason ?? null);
        return {
            user,
            latestAttempt: savedAttempt,
        };
    }
    extractProviderStatus(body) {
        return body?.status ?? body?.decision?.status ?? "";
    }
    mapProviderStatus(status) {
        const s = String(status || "").trim().toLowerCase();
        const needsReview = [
            "approved", "verified", "success", "completed", "complete", "passed", "active",
            "declined", "rejected", "failed",
            "needs review", "needs_review", "review", "manual_review",
        ];
        if (needsReview.includes(s))
            return kyc_status_enum_1.KycVerificationStatus.NEEDS_REVIEW;
        const pending = ["in review", "in_review", "in progress", "in_progress", "processing"];
        if (pending.includes(s))
            return kyc_status_enum_1.KycVerificationStatus.PENDING;
        return kyc_status_enum_1.KycVerificationStatus.PENDING;
    }
    isFinalStatus(status) {
        return [
            kyc_status_enum_1.KycVerificationStatus.VERIFIED,
            kyc_status_enum_1.KycVerificationStatus.REJECTED,
            kyc_status_enum_1.KycVerificationStatus.NEEDS_REVIEW,
        ].includes(status);
    }
    extractProviderSessionId(session) {
        return session?.session_id || session?.id || null;
    }
    extractRejectionReason(body) {
        const warnings = body?.decision?.id_verifications?.[0]?.warnings?.[0];
        return (warnings?.short_description ||
            warnings?.long_description ||
            warnings?.risk ||
            body?.decision?.reviews?.[body?.decision?.reviews?.length - 1]?.comment ||
            null);
    }
    normalizeMetadata(value) {
        return JSON.parse(JSON.stringify(value));
    }
    async sendKycStatusNotification(user, status, rejectionReason) {
        const token = String(user?.deviceId || "").trim();
        if (!token || token === "unknown_device") {
            return;
        }
        const payload = this.getNotificationPayload(status, rejectionReason);
        if (!payload) {
            return;
        }
        try {
            await this.firebaseService.sendNotification(token, payload.title, payload.body, "KYC", {
                screen: "KYC",
                kycStatus: status,
                userId: String(user.id),
            });
        }
        catch (error) {
            this.logger.warn(`Failed to send KYC notification to user ${user.id}: ${error?.message ?? error}`);
        }
        if (status === kyc_status_enum_1.KycVerificationStatus.VERIFIED) {
            try {
                const loginLink = `https://cars247.ae/cars24x7admin/authentication/sign-in`;
                await this.emailService.sendMakeVendorEmail(user.email, loginLink);
                this.logger.log(`Vendor invitation email sent to ${user.email}`);
            }
            catch (error) {
                this.logger.warn(`Failed to send vendor invitation email to ${user.email}: ${error?.message ?? error}`);
            }
        }
    }
    getNotificationPayload(status, rejectionReason) {
        if (status === kyc_status_enum_1.KycVerificationStatus.VERIFIED) {
            return {
                title: "KYC approved",
                body: "Your identity verification was approved. You can now publish listings.",
            };
        }
        if (status === kyc_status_enum_1.KycVerificationStatus.REJECTED) {
            return {
                title: "KYC rejected",
                body: rejectionReason
                    ? `Your verification was rejected: ${rejectionReason}`
                    : "Your verification was rejected. Please review and retry.",
            };
        }
        if (status === kyc_status_enum_1.KycVerificationStatus.NEEDS_REVIEW) {
            return {
                title: "KYC under review",
                body: "Your verification needs additional review. We will update you shortly.",
            };
        }
        return null;
    }
};
exports.KycVerificationService = KycVerificationService;
exports.KycVerificationService = KycVerificationService = KycVerificationService_1 = __decorate([
    (0, common_1.Injectable)(),
    __param(4, (0, typeorm_1.InjectRepository)(kyc_verification_attempt_entity_1.KycVerificationAttempt)),
    __param(5, (0, typeorm_1.InjectRepository)(user_entity_1.User)),
    __param(6, (0, typeorm_1.InjectRepository)(vendor_plan_order_entity_1.VendorPlanOrder)),
    __param(7, (0, typeorm_1.InjectRepository)(vendor_entity_1.Vendor)),
    __param(8, (0, typeorm_1.InjectRepository)(vendor_plan_entity_1.VendorPlan)),
    __metadata("design:paramtypes", [didit_service_1.DiditService,
        user_services_1.UserService,
        firebase_service_1.FirebaseService,
        otp_email_service_1.emailOtpService,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository])
], KycVerificationService);
//# sourceMappingURL=kyc-verification.service.js.map