"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 PaymentsService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PaymentsService = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const payment_entity_1 = require("../database/entities/payment.entity");
const payment_attempt_entity_1 = require("../database/entities/payment-attempt.entity");
const mpesa_callback_entity_1 = require("../database/entities/mpesa-callback.entity");
const mpesa_reconciliation_entity_1 = require("../database/entities/mpesa-reconciliation.entity");
const receipt_entity_1 = require("../database/entities/receipt.entity");
const invoice_entity_1 = require("../database/entities/invoice.entity");
const transaction_entity_1 = require("../database/entities/transaction.entity");
const customer_entity_1 = require("../database/entities/customer.entity");
const wifi_session_entity_1 = require("../database/entities/wifi-session.entity");
const business_entity_1 = require("../database/entities/business.entity");
const audit_log_entity_1 = require("../database/entities/audit-log.entity");
const state_machines_1 = require("../common/constants/state-machines");
const id_generator_util_1 = require("../common/utils/id-generator.util");
const mpesa_service_1 = require("../mpesa/mpesa.service");
const receipt_service_1 = require("./receipt.service");
const sync_1 = require("csv-stringify/sync");
const PAYMENT_STATUS_TO_COMMON = {
    [payment_entity_1.PaymentStatus.CREATED]: state_machines_1.PAYMENT_STATUSES.INITIATED,
    [payment_entity_1.PaymentStatus.PENDING]: state_machines_1.PAYMENT_STATUSES.PENDING_CONFIRMATION,
    [payment_entity_1.PaymentStatus.STK_SENT]: state_machines_1.PAYMENT_STATUSES.PROCESSING,
    [payment_entity_1.PaymentStatus.PROCESSING]: state_machines_1.PAYMENT_STATUSES.PROCESSING,
    [payment_entity_1.PaymentStatus.SUCCESSFUL]: state_machines_1.PAYMENT_STATUSES.COMPLETED,
    [payment_entity_1.PaymentStatus.FAILED]: state_machines_1.PAYMENT_STATUSES.FAILED,
    [payment_entity_1.PaymentStatus.CANCELLED]: state_machines_1.PAYMENT_STATUSES.CANCELLED,
    [payment_entity_1.PaymentStatus.TIMED_OUT]: state_machines_1.PAYMENT_STATUSES.EXPIRED,
    [payment_entity_1.PaymentStatus.REVERSED]: state_machines_1.PAYMENT_STATUSES.REFUNDED,
    [payment_entity_1.PaymentStatus.RECONCILIATION_REQUIRED]: state_machines_1.PAYMENT_STATUSES.PROCESSING,
};
const COMMON_TO_PAYMENT_STATUS = {
    [state_machines_1.PAYMENT_STATUSES.INITIATED]: payment_entity_1.PaymentStatus.CREATED,
    [state_machines_1.PAYMENT_STATUSES.PENDING_CONFIRMATION]: payment_entity_1.PaymentStatus.PENDING,
    [state_machines_1.PAYMENT_STATUSES.PROCESSING]: payment_entity_1.PaymentStatus.PROCESSING,
    [state_machines_1.PAYMENT_STATUSES.COMPLETED]: payment_entity_1.PaymentStatus.SUCCESSFUL,
    [state_machines_1.PAYMENT_STATUSES.FAILED]: payment_entity_1.PaymentStatus.FAILED,
    [state_machines_1.PAYMENT_STATUSES.CANCELLED]: payment_entity_1.PaymentStatus.CANCELLED,
    [state_machines_1.PAYMENT_STATUSES.REFUNDED]: payment_entity_1.PaymentStatus.REVERSED,
    [state_machines_1.PAYMENT_STATUSES.PARTIALLY_REFUNDED]: payment_entity_1.PaymentStatus.REVERSED,
    [state_machines_1.PAYMENT_STATUSES.EXPIRED]: payment_entity_1.PaymentStatus.TIMED_OUT,
};
const PAYMENT_VALID_TRANSITIONS = {
    [payment_entity_1.PaymentStatus.CREATED]: [
        payment_entity_1.PaymentStatus.STK_SENT,
        payment_entity_1.PaymentStatus.FAILED,
        payment_entity_1.PaymentStatus.CANCELLED,
    ],
    [payment_entity_1.PaymentStatus.PENDING]: [
        payment_entity_1.PaymentStatus.PROCESSING,
        payment_entity_1.PaymentStatus.SUCCESSFUL,
        payment_entity_1.PaymentStatus.FAILED,
        payment_entity_1.PaymentStatus.CANCELLED,
        payment_entity_1.PaymentStatus.TIMED_OUT,
    ],
    [payment_entity_1.PaymentStatus.STK_SENT]: [
        payment_entity_1.PaymentStatus.PENDING,
        payment_entity_1.PaymentStatus.PROCESSING,
        payment_entity_1.PaymentStatus.SUCCESSFUL,
        payment_entity_1.PaymentStatus.FAILED,
        payment_entity_1.PaymentStatus.CANCELLED,
        payment_entity_1.PaymentStatus.TIMED_OUT,
        payment_entity_1.PaymentStatus.RECONCILIATION_REQUIRED,
    ],
    [payment_entity_1.PaymentStatus.PROCESSING]: [
        payment_entity_1.PaymentStatus.SUCCESSFUL,
        payment_entity_1.PaymentStatus.FAILED,
        payment_entity_1.PaymentStatus.CANCELLED,
    ],
    [payment_entity_1.PaymentStatus.SUCCESSFUL]: [payment_entity_1.PaymentStatus.REVERSED],
    [payment_entity_1.PaymentStatus.FAILED]: [payment_entity_1.PaymentStatus.CREATED],
    [payment_entity_1.PaymentStatus.CANCELLED]: [],
    [payment_entity_1.PaymentStatus.TIMED_OUT]: [],
    [payment_entity_1.PaymentStatus.REVERSED]: [],
    [payment_entity_1.PaymentStatus.RECONCILIATION_REQUIRED]: [
        payment_entity_1.PaymentStatus.SUCCESSFUL,
        payment_entity_1.PaymentStatus.FAILED,
    ],
};
let PaymentsService = PaymentsService_1 = class PaymentsService {
    constructor(paymentRepo, paymentAttemptRepo, mpesaCallbackRepo, mpesaReconciliationRepo, receiptRepo, invoiceRepo, transactionRepo, customerRepo, wifiSessionRepo, businessRepo, auditLogRepo, dataSource, mpesaService, receiptService) {
        this.paymentRepo = paymentRepo;
        this.paymentAttemptRepo = paymentAttemptRepo;
        this.mpesaCallbackRepo = mpesaCallbackRepo;
        this.mpesaReconciliationRepo = mpesaReconciliationRepo;
        this.receiptRepo = receiptRepo;
        this.invoiceRepo = invoiceRepo;
        this.transactionRepo = transactionRepo;
        this.customerRepo = customerRepo;
        this.wifiSessionRepo = wifiSessionRepo;
        this.businessRepo = businessRepo;
        this.auditLogRepo = auditLogRepo;
        this.dataSource = dataSource;
        this.mpesaService = mpesaService;
        this.receiptService = receiptService;
        this.logger = new common_1.Logger(PaymentsService_1.name);
    }
    async findAll(businessId, filters) {
        const page = filters.page ?? 1;
        const limit = filters.limit ?? 20;
        const sortBy = filters.sortBy ?? 'createdAt';
        const sortOrder = filters.sortOrder ?? 'DESC';
        const where = { businessId };
        if (filters.status) {
            where.status = filters.status;
        }
        if (filters.customerId) {
            where.customerId = filters.customerId;
        }
        if (filters.routerId) {
            where.routerId = filters.routerId;
        }
        if (filters.packageId) {
            where.packageId = filters.packageId;
        }
        const combinedWhere = [where];
        if (filters.startDate && filters.endDate) {
            combinedWhere[0].createdAt = (0, typeorm_2.Between)(filters.startDate, filters.endDate);
        }
        else if (filters.startDate) {
            combinedWhere[0].createdAt = (0, typeorm_2.MoreThanOrEqual)(filters.startDate);
        }
        else if (filters.endDate) {
            combinedWhere[0].createdAt = (0, typeorm_2.LessThanOrEqual)(filters.endDate);
        }
        if (filters.minAmount !== undefined && filters.maxAmount !== undefined) {
            combinedWhere[0].amount = (0, typeorm_2.Between)(filters.minAmount, filters.maxAmount);
        }
        else if (filters.minAmount !== undefined) {
            combinedWhere[0].amount = (0, typeorm_2.MoreThanOrEqual)(filters.minAmount);
        }
        else if (filters.maxAmount !== undefined) {
            combinedWhere[0].amount = (0, typeorm_2.LessThanOrEqual)(filters.maxAmount);
        }
        const [data, total] = await this.paymentRepo.findAndCount({
            where: combinedWhere,
            relations: PaymentsService_1.PAYMENT_RELATIONS,
            order: { [sortBy]: sortOrder },
            skip: (page - 1) * limit,
            take: limit,
        });
        const totalPages = Math.ceil(total / limit);
        return { data, total, page, limit, totalPages };
    }
    async findOne(id, businessId) {
        const payment = await this.paymentRepo.findOne({
            where: { id, businessId },
            relations: PaymentsService_1.PAYMENT_RELATIONS,
        });
        if (!payment) {
            throw new common_1.NotFoundException(`Payment with ID ${id} not found`);
        }
        return payment;
    }
    async findByInternalRef(ref, businessId) {
        const payment = await this.paymentRepo.findOne({
            where: { internalReference: ref, businessId },
            relations: PaymentsService_1.PAYMENT_RELATIONS,
        });
        if (!payment) {
            throw new common_1.NotFoundException(`Payment with reference ${ref} not found`);
        }
        return payment;
    }
    async findByMpesaReceipt(receipt, businessId) {
        const payment = await this.paymentRepo.findOne({
            where: { mpesaReceipt: receipt, businessId },
            relations: PaymentsService_1.PAYMENT_RELATIONS,
        });
        if (!payment) {
            throw new common_1.NotFoundException(`Payment with M-Pesa receipt ${receipt} not found`);
        }
        return payment;
    }
    async getPaymentStats(businessId, filters) {
        const where = { businessId };
        if (filters.startDate && filters.endDate) {
            where.createdAt = (0, typeorm_2.Between)(filters.startDate, filters.endDate);
        }
        else if (filters.startDate) {
            where.createdAt = (0, typeorm_2.MoreThanOrEqual)(filters.startDate);
        }
        else if (filters.endDate) {
            where.createdAt = (0, typeorm_2.LessThanOrEqual)(filters.endDate);
        }
        const payments = await this.paymentRepo.find({ where });
        const successful = payments.filter((p) => p.status === payment_entity_1.PaymentStatus.SUCCESSFUL);
        const failed = payments.filter((p) => p.status === payment_entity_1.PaymentStatus.FAILED);
        const pending = payments.filter((p) => p.status === payment_entity_1.PaymentStatus.CREATED ||
            p.status === payment_entity_1.PaymentStatus.PENDING ||
            p.status === payment_entity_1.PaymentStatus.STK_SENT ||
            p.status === payment_entity_1.PaymentStatus.PROCESSING);
        const totalRevenue = successful.reduce((sum, p) => sum + Number(p.amount), 0);
        const totalAmount = payments.reduce((sum, p) => sum + Number(p.amount), 0);
        return {
            totalRevenue,
            successfulCount: successful.length,
            failedCount: failed.length,
            pendingCount: pending.length,
            totalAmount,
            averageAmount: payments.length > 0 ? totalAmount / payments.length : 0,
        };
    }
    async getRecentTransactions(businessId, limit = 10) {
        return this.paymentRepo.find({
            where: { businessId },
            relations: PaymentsService_1.PAYMENT_RELATIONS,
            order: { createdAt: 'DESC' },
            take: limit,
        });
    }
    async getTransactionReconciliation(businessId) {
        return this.paymentRepo.find({
            where: [
                { businessId, reconciled: false, status: payment_entity_1.PaymentStatus.SUCCESSFUL },
                { businessId, reconciled: false, status: payment_entity_1.PaymentStatus.STK_SENT },
                { businessId, reconciled: false, status: payment_entity_1.PaymentStatus.RECONCILIATION_REQUIRED },
            ],
            relations: PaymentsService_1.PAYMENT_RELATIONS,
            order: { createdAt: 'DESC' },
        });
    }
    async exportPaymentsCsv(businessId, filters) {
        const { data } = await this.findAll(businessId, { ...filters, page: 1, limit: 10000 });
        const rows = data.map((payment) => ({
            'Payment ID': payment.id,
            'Internal Reference': payment.internalReference,
            'M-Pesa Receipt': payment.mpesaReceipt || '',
            'Phone Number': payment.phoneNumber,
            Amount: Number(payment.amount).toFixed(2),
            Status: payment.status,
            Customer: payment.customer
                ? `${payment.customer.firstName || ''} ${payment.customer.lastName || ''}`.trim()
                : '',
            'Package Name': payment.package?.name || '',
            'Created At': payment.createdAt?.toISOString() || '',
            'Reconciled': payment.reconciled ? 'Yes' : 'No',
        }));
        if (rows.length === 0) {
            return 'Payment ID,Internal Reference,M-Pesa Receipt,Phone Number,Amount,Status,Customer,Package Name,Created At,Reconciled\n';
        }
        return (0, sync_1.stringify)(rows, {
            header: true,
            columns: [
                'Payment ID',
                'Internal Reference',
                'M-Pesa Receipt',
                'Phone Number',
                'Amount',
                'Status',
                'Customer',
                'Package Name',
                'Created At',
                'Reconciled',
            ],
        });
    }
    async generateReceipt(paymentId, businessId) {
        const payment = await this.findOne(paymentId, businessId);
        return this.receiptService.generateReceipt(payment.id);
    }
    async processCallback(payload) {
        return this.mpesaService.handleCallback(payload);
    }
    async recordPaymentAttempt(paymentId, attempt) {
        const attemptRecord = this.paymentAttemptRepo.create({
            paymentId,
            attemptNumber: attempt.attemptNumber,
            status: attempt.status,
            requestPayload: attempt.requestPayload,
            responsePayload: attempt.responsePayload,
            errorMessage: attempt.errorMessage,
        });
        return this.paymentAttemptRepo.save(attemptRecord);
    }
    async updatePaymentStatus(paymentId, status, metadata) {
        const payment = await this.paymentRepo.findOne({ where: { id: paymentId } });
        if (!payment) {
            throw new common_1.NotFoundException(`Payment with ID ${paymentId} not found`);
        }
        const currentCommonStatus = PAYMENT_STATUS_TO_COMMON[payment.status];
        if (currentCommonStatus === undefined) {
            throw new common_1.BadRequestException(`Unknown current payment status: ${payment.status}`);
        }
        this.validateStateTransition(currentCommonStatus, status);
        const targetStatus = COMMON_TO_PAYMENT_STATUS[status];
        if (targetStatus === undefined) {
            throw new common_1.BadRequestException(`Cannot map status ${status} to a valid payment status`);
        }
        await this.dataSource.transaction(async (manager) => {
            const txPaymentRepo = manager.getRepository(payment_entity_1.Payment);
            const txAuditLogRepo = manager.getRepository(audit_log_entity_1.AuditLog);
            const txReceiptRepo = manager.getRepository(receipt_entity_1.Receipt);
            const txTransactionRepo = manager.getRepository(transaction_entity_1.Transaction);
            const txWifiSessionRepo = manager.getRepository(wifi_session_entity_1.WifiSession);
            payment.status = targetStatus;
            if (metadata) {
                if (metadata.notes) {
                    payment.notes = metadata.notes;
                }
                if (metadata.resultCode !== undefined) {
                    payment.resultCode = metadata.resultCode;
                }
                if (metadata.resultDescription) {
                    payment.resultDescription = metadata.resultDescription;
                }
                if (metadata.mpesaReceipt) {
                    payment.mpesaReceipt = metadata.mpesaReceipt;
                    payment.reconciled = true;
                    payment.reconciledAt = new Date();
                }
                if (metadata.reconciled !== undefined) {
                    payment.reconciled = metadata.reconciled;
                    if (metadata.reconciled) {
                        payment.reconciledAt = new Date();
                    }
                }
            }
            await txPaymentRepo.save(payment);
            if (targetStatus === payment_entity_1.PaymentStatus.SUCCESSFUL) {
                const existingReceipt = await txReceiptRepo.findOne({
                    where: { paymentId: payment.id },
                });
                if (!existingReceipt && payment.mpesaReceipt) {
                    const receiptNumber = `${(0, id_generator_util_1.generatePaymentReference)()}-RCP`;
                    const receipt = txReceiptRepo.create({
                        businessId: payment.businessId,
                        paymentId: payment.id,
                        customerId: payment.customerId,
                        receiptNumber,
                        amount: payment.amount,
                        paymentMethod: 'MPESA',
                        mpesaReceipt: payment.mpesaReceipt,
                    });
                    await txReceiptRepo.save(receipt);
                }
                const existingTransaction = await txTransactionRepo.findOne({
                    where: { paymentId: payment.id },
                });
                if (!existingTransaction) {
                    const transaction = txTransactionRepo.create({
                        businessId: payment.businessId,
                        customerId: payment.customerId,
                        paymentId: payment.id,
                        type: 'PAYMENT',
                        amount: payment.amount,
                        description: `M-Pesa payment: ${payment.mpesaReceipt || payment.internalReference}`,
                        referenceId: payment.mpesaReceipt || payment.internalReference,
                    });
                    await txTransactionRepo.save(transaction);
                }
                if (payment.routerId && payment.customerId) {
                    const hasActiveSession = await txWifiSessionRepo.findOne({
                        where: {
                            customerId: payment.customerId,
                            packageId: payment.packageId,
                            routerId: payment.routerId,
                            businessId: payment.businessId,
                        },
                    });
                    if (!hasActiveSession) {
                        const session = txWifiSessionRepo.create({
                            customerId: payment.customerId,
                            packageId: payment.packageId,
                            routerId: payment.routerId,
                            businessId: payment.businessId,
                            status: 'PENDING_ACTIVATION',
                        });
                        await txWifiSessionRepo.save(session);
                    }
                }
            }
            const auditLog = txAuditLogRepo.create({
                businessId: payment.businessId,
                action: `PAYMENT_STATUS_CHANGED`,
                entityType: 'Payment',
                entityId: payment.id,
                newValue: {
                    previousStatus: currentCommonStatus,
                    newStatus: status,
                    targetEntityStatus: targetStatus,
                    ...metadata,
                },
            });
            await txAuditLogRepo.save(auditLog);
        });
        this.logger.log(`Payment ${paymentId} status updated: ${currentCommonStatus} -> ${status} (entity: ${payment.status})`);
        return this.paymentRepo.findOne({
            where: { id: paymentId },
            relations: PaymentsService_1.PAYMENT_RELATIONS,
        });
    }
    async retryActivation(paymentId) {
        const payment = await this.paymentRepo.findOne({
            where: { id: paymentId },
            relations: ['customer', 'package'],
        });
        if (!payment) {
            throw new common_1.NotFoundException(`Payment with ID ${paymentId} not found`);
        }
        if (payment.status !== payment_entity_1.PaymentStatus.SUCCESSFUL) {
            throw new common_1.BadRequestException(`Cannot retry activation for payment with status: ${payment.status}. Payment must be SUCCESSFUL.`);
        }
        if (!payment.routerId || !payment.customerId) {
            throw new common_1.BadRequestException('Payment does not have associated router or customer. Cannot create session.');
        }
        const existingSession = await this.wifiSessionRepo.findOne({
            where: {
                customerId: payment.customerId,
                packageId: payment.packageId,
                routerId: payment.routerId,
                businessId: payment.businessId,
            },
            order: { createdAt: 'DESC' },
        });
        if (existingSession && existingSession.status === 'ACTIVE') {
            return {
                success: true,
                message: 'Session is already active',
                sessionId: existingSession.id,
            };
        }
        const session = this.wifiSessionRepo.create({
            customerId: payment.customerId,
            packageId: payment.packageId,
            routerId: payment.routerId,
            businessId: payment.businessId,
            status: 'PENDING_ACTIVATION',
        });
        const savedSession = await this.wifiSessionRepo.save(session);
        this.logger.log(`Activation retry triggered for payment ${paymentId}: session ${savedSession.id} created`);
        await this.auditLogRepo.save({
            businessId: payment.businessId,
            action: 'RETRY_ACTIVATION',
            entityType: 'Payment',
            entityId: payment.id,
            newValue: { sessionId: savedSession.id },
        });
        return {
            success: true,
            message: 'Session created and pending activation',
            sessionId: savedSession.id,
        };
    }
    validateStateTransition(currentStatus, targetStatus) {
        const allowedTransitions = state_machines_1.PAYMENT_STATE_TRANSITIONS[currentStatus];
        if (!allowedTransitions) {
            throw new common_1.BadRequestException(`No transition rules defined for status: ${currentStatus}`);
        }
        if (!allowedTransitions.includes(targetStatus)) {
            throw new common_1.BadRequestException(`Invalid state transition from ${currentStatus} to ${targetStatus}. Allowed transitions: ${allowedTransitions.join(', ')}`);
        }
    }
};
exports.PaymentsService = PaymentsService;
PaymentsService.PAYMENT_RELATIONS = [
    'customer',
    'package',
    'business',
];
exports.PaymentsService = PaymentsService = PaymentsService_1 = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, typeorm_1.InjectRepository)(payment_entity_1.Payment)),
    __param(1, (0, typeorm_1.InjectRepository)(payment_attempt_entity_1.PaymentAttempt)),
    __param(2, (0, typeorm_1.InjectRepository)(mpesa_callback_entity_1.MpesaCallback)),
    __param(3, (0, typeorm_1.InjectRepository)(mpesa_reconciliation_entity_1.MpesaReconciliation)),
    __param(4, (0, typeorm_1.InjectRepository)(receipt_entity_1.Receipt)),
    __param(5, (0, typeorm_1.InjectRepository)(invoice_entity_1.Invoice)),
    __param(6, (0, typeorm_1.InjectRepository)(transaction_entity_1.Transaction)),
    __param(7, (0, typeorm_1.InjectRepository)(customer_entity_1.Customer)),
    __param(8, (0, typeorm_1.InjectRepository)(wifi_session_entity_1.WifiSession)),
    __param(9, (0, typeorm_1.InjectRepository)(business_entity_1.Business)),
    __param(10, (0, typeorm_1.InjectRepository)(audit_log_entity_1.AuditLog)),
    __metadata("design:paramtypes", [typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.DataSource,
        mpesa_service_1.MpesaService,
        receipt_service_1.ReceiptService])
], PaymentsService);
