"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 MpesaService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MpesaService = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const config_service_1 = require("../common/config/config.service");
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 system_settings_entity_1 = require("../database/entities/system-settings.entity");
const business_settings_entity_1 = require("../database/entities/business-settings.entity");
const customer_entity_1 = require("../database/entities/customer.entity");
const wifi_session_entity_1 = require("../database/entities/wifi-session.entity");
const audit_log_entity_1 = require("../database/entities/audit-log.entity");
const receipt_entity_1 = require("../database/entities/receipt.entity");
const transaction_entity_1 = require("../database/entities/transaction.entity");
const phone_util_1 = require("../common/utils/phone.util");
const id_generator_util_1 = require("../common/utils/id-generator.util");
const mpesa_exception_1 = require("./mpesa.exception");
const axios_1 = require("axios");
const Redis = require("ioredis");
let MpesaService = MpesaService_1 = class MpesaService {
    constructor(paymentRepo, paymentAttemptRepo, mpesaCallbackRepo, mpesaReconciliationRepo, systemSettingsRepo, businessSettingsRepo, customerRepo, wifiSessionRepo, auditLogRepo, receiptRepo, transactionRepo, configService, dataSource) {
        this.paymentRepo = paymentRepo;
        this.paymentAttemptRepo = paymentAttemptRepo;
        this.mpesaCallbackRepo = mpesaCallbackRepo;
        this.mpesaReconciliationRepo = mpesaReconciliationRepo;
        this.systemSettingsRepo = systemSettingsRepo;
        this.businessSettingsRepo = businessSettingsRepo;
        this.customerRepo = customerRepo;
        this.wifiSessionRepo = wifiSessionRepo;
        this.auditLogRepo = auditLogRepo;
        this.receiptRepo = receiptRepo;
        this.transactionRepo = transactionRepo;
        this.configService = configService;
        this.dataSource = dataSource;
        this.logger = new common_1.Logger(MpesaService_1.name);
        this.mpesaConfig = this.configService.getMpesaConfig();
        this.axiosInstance = axios_1.default.create({
            baseURL: this.mpesaConfig.baseUrl || 'https://sandbox.safaricom.co.ke',
            timeout: 30000,
            headers: { 'Content-Type': 'application/json' },
        });
        this.axiosInstance.interceptors.request.use((config) => {
            const maskedUrl = config.url?.replace(/(Bearer\s+)[^\s]+/g, '$1***MASKED***');
            this.logger.log(`M-Pesa API Request: ${config.method?.toUpperCase()} ${maskedUrl}`);
            if (config.data) {
                const maskedData = JSON.stringify(config.data).replace(/"Password":"[^"]+"/g, '"Password":"***MASKED***"');
                this.logger.debug(`Request Body: ${maskedData}`);
            }
            return config;
        });
        this.axiosInstance.interceptors.response.use((response) => {
            this.logger.log(`M-Pesa API Response: ${response.status} ${response.statusText}`);
            return response;
        }, (error) => {
            const status = error.response?.status;
            const data = error.response?.data;
            this.logger.error(`M-Pesa API Error: ${status} - ${JSON.stringify(data)}`);
            return Promise.reject(error);
        });
        const redisConfig = this.configService.getRedisConfig();
        this.redisClient = new Redis.default({
            host: redisConfig.host,
            port: redisConfig.port,
            password: redisConfig.password || undefined,
            db: redisConfig.db,
            maxRetriesPerRequest: 3,
            retryStrategy: (times) => Math.min(times * 100, 3000),
            lazyConnect: true,
        });
        this.redisClient.on('error', (err) => {
            this.logger.warn(`Redis connection issue: ${err.message}`);
        });
    }
    async getAccessToken() {
        try {
            const cached = await this.redisClient.get(MpesaService_1.ACCESS_TOKEN_CACHE_KEY);
            if (cached) {
                this.logger.debug('Using cached M-Pesa access token');
                return cached;
            }
        }
        catch (err) {
            this.logger.warn(`Redis read failed, falling back to new token: ${err.message}`);
        }
        const consumerKey = this.mpesaConfig.consumerKey;
        const consumerSecret = this.mpesaConfig.consumerSecret;
        if (!consumerKey || !consumerSecret) {
            throw mpesa_exception_1.MpesaException.configurationError('M-Pesa consumer key and secret are required');
        }
        const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString('base64');
        const response = await this.axiosInstance.get('/oauth/v1/generate?grant_type=client_credentials', {
            headers: { Authorization: `Basic ${auth}` },
        });
        const token = response.data.access_token;
        const expiresIn = parseInt(response.data.expires_in, 10) || 3600;
        const ttl = Math.max(expiresIn - MpesaService_1.TOKEN_EXPIRY_BUFFER_SECONDS, 10);
        try {
            await this.redisClient.set(MpesaService_1.ACCESS_TOKEN_CACHE_KEY, token, 'EX', ttl);
        }
        catch (err) {
            this.logger.warn(`Failed to cache access token: ${err.message}`);
        }
        this.logger.log('M-Pesa access token obtained successfully');
        return token;
    }
    async initiateStkPush(dto, businessId, customerId) {
        const phoneNumber = (0, phone_util_1.formatForMpesa)(dto.phoneNumber);
        const internalReference = (0, id_generator_util_1.generatePaymentReference)();
        const timestamp = this.getTimestamp();
        const password = this.generateMpesaPassword(this.mpesaConfig.shortCode, this.mpesaConfig.passkey, timestamp);
        const callbackUrl = this.buildCallbackUrl();
        const payment = this.paymentRepo.create({
            businessId,
            customerId: customerId || undefined,
            packageId: dto.packageId,
            routerId: dto.routerId || undefined,
            phoneNumber,
            amount: dto.amount,
            internalReference,
            status: payment_entity_1.PaymentStatus.CREATED,
        });
        await this.paymentRepo.save(payment);
        const requestPayload = {
            BusinessShortCode: this.mpesaConfig.shortCode,
            Password: password,
            Timestamp: timestamp,
            TransactionType: this.mpesaConfig.transactionType || 'CustomerPayBillOnline',
            Amount: Math.round(dto.amount),
            PartyA: phoneNumber,
            PartyB: this.mpesaConfig.shortCode,
            PhoneNumber: phoneNumber,
            CallBackURL: callbackUrl,
            AccountReference: dto.accountReference || internalReference,
            TransactionDesc: dto.transactionDesc || 'WiFi Payment',
        };
        this.logger.log(`Initiating STK Push for ${phoneNumber}, amount ${dto.amount}, ref ${internalReference}`);
        const attemptNumber = 1;
        try {
            const response = await this.axiosInstance.post(MpesaService_1.STK_PUSH_PATH, requestPayload);
            const result = response.data;
            payment.merchantRequestId = result.MerchantRequestID;
            payment.checkoutRequestId = result.CheckoutRequestID;
            payment.status = payment_entity_1.PaymentStatus.STK_SENT;
            payment.attempts = attemptNumber;
            payment.lastAttemptAt = new Date();
            await this.paymentRepo.save(payment);
            await this.paymentAttemptRepo.save({
                paymentId: payment.id,
                attemptNumber,
                status: 'STK_SENT',
                requestPayload,
                responsePayload: result,
            });
            const timeoutSeconds = this.configService.getNumber('MPESA_RECONCILE_TIMEOUT', 30);
            setTimeout(() => {
                this.reconcilePayment(payment.id).catch((err) => {
                    this.logger.error(`Auto-reconciliation failed for payment ${payment.id}: ${err.message}`);
                });
            }, timeoutSeconds * 1000);
            return {
                checkoutRequestId: result.CheckoutRequestID,
                merchantRequestId: result.MerchantRequestID,
                internalReference,
                customerMessage: result.CustomerMessage,
            };
        }
        catch (error) {
            const errorMessage = error.response?.data?.errorMessage ||
                error.response?.data?.ResponseDescription ||
                error.message;
            payment.status = payment_entity_1.PaymentStatus.FAILED;
            payment.notes = errorMessage;
            await this.paymentRepo.save(payment);
            await this.paymentAttemptRepo.save({
                paymentId: payment.id,
                attemptNumber,
                status: 'FAILED',
                requestPayload,
                errorMessage,
            });
            this.logger.error(`STK Push failed for payment ${payment.id}: ${errorMessage}`);
            throw mpesa_exception_1.MpesaException.stkPushFailed(errorMessage);
        }
    }
    async handleCallback(payload) {
        try {
            if (!payload?.Body?.stkCallback) {
                this.logger.warn('Invalid callback payload structure received');
                return { success: true, message: 'Invalid payload structure' };
            }
            const callback = payload.Body.stkCallback;
            const { MerchantRequestID, CheckoutRequestID, ResultCode, ResultDesc, CallbackMetadata, } = callback;
            if (!CheckoutRequestID) {
                this.logger.warn('Callback missing CheckoutRequestID');
                return { success: true, message: 'Missing CheckoutRequestID' };
            }
            let mpesaReceipt = null;
            let phoneNumber = null;
            let amount = null;
            if (CallbackMetadata?.Item && Array.isArray(CallbackMetadata.Item)) {
                for (const item of CallbackMetadata.Item) {
                    switch (item.Name) {
                        case 'MpesaReceiptNumber':
                            mpesaReceipt = item.Value?.toString() || null;
                            break;
                        case 'PhoneNumber':
                            phoneNumber = item.Value?.toString() || null;
                            break;
                        case 'Amount':
                            amount = item.Value ? parseFloat(item.Value.toString()) : null;
                            break;
                    }
                }
            }
            const callbackRecord = this.mpesaCallbackRepo.create({
                checkoutRequestId: CheckoutRequestID,
                merchantRequestId: MerchantRequestID || undefined,
                resultCode: ResultCode,
                resultDescription: ResultDesc || undefined,
                mpesaReceipt: mpesaReceipt || undefined,
                phoneNumber: phoneNumber || undefined,
                amount: amount || undefined,
                rawPayload: payload,
                receivedAt: new Date(),
                processed: false,
                duplicate: false,
            });
            const existingProcessed = await this.mpesaCallbackRepo.findOne({
                where: { checkoutRequestId: CheckoutRequestID, processed: true },
            });
            if (existingProcessed) {
                callbackRecord.duplicate = true;
                await this.mpesaCallbackRepo.save(callbackRecord);
                this.logger.warn(`Duplicate callback for CheckoutRequestID: ${CheckoutRequestID}`);
                return { success: true, message: 'Duplicate callback' };
            }
            await this.mpesaCallbackRepo.save(callbackRecord);
            const payment = await this.paymentRepo.findOne({
                where: { checkoutRequestId: CheckoutRequestID },
            });
            if (!payment) {
                this.logger.warn(`No payment found for CheckoutRequestID: ${CheckoutRequestID}. Stored for reconciliation.`);
                return { success: true, message: 'Payment not found - queued for reconciliation' };
            }
            await this.dataSource.transaction(async (manager) => {
                const paymentRepo = manager.getRepository(payment_entity_1.Payment);
                const receiptRepo = manager.getRepository(receipt_entity_1.Receipt);
                const transactionRepo = manager.getRepository(transaction_entity_1.Transaction);
                const auditLogRepo = manager.getRepository(audit_log_entity_1.AuditLog);
                const wifiSessionRepo = manager.getRepository(wifi_session_entity_1.WifiSession);
                const callbackRepo = manager.getRepository(mpesa_callback_entity_1.MpesaCallback);
                const reconciliationRepo = manager.getRepository(mpesa_reconciliation_entity_1.MpesaReconciliation);
                payment.mpesaCallbackReceived = true;
                payment.resultCode = ResultCode;
                payment.resultDescription = (ResultDesc || undefined);
                if (ResultCode === 0) {
                    payment.status = payment_entity_1.PaymentStatus.SUCCESSFUL;
                    payment.mpesaReceipt = (mpesaReceipt || undefined);
                    payment.reconciled = true;
                    payment.reconciledAt = new Date();
                    payment.callbackPayload = callback;
                    await paymentRepo.save(payment);
                    if (mpesaReceipt) {
                        const receiptNumber = (0, id_generator_util_1.generateReceiptNumber)();
                        const receipt = receiptRepo.create({
                            businessId: payment.businessId,
                            paymentId: payment.id,
                            customerId: payment.customerId,
                            receiptNumber,
                            amount: payment.amount,
                            paymentMethod: 'MPESA',
                            mpesaReceipt,
                        });
                        await receiptRepo.save(receipt);
                    }
                    const transaction = transactionRepo.create({
                        businessId: payment.businessId,
                        customerId: payment.customerId,
                        paymentId: payment.id,
                        type: transaction_entity_1.TransactionType.PAYMENT,
                        amount: payment.amount,
                        description: `M-Pesa payment: ${mpesaReceipt || payment.internalReference}`,
                        referenceId: mpesaReceipt || payment.internalReference,
                    });
                    await transactionRepo.save(transaction);
                    const auditLog = auditLogRepo.create({
                        businessId: payment.businessId,
                        action: 'MPESA_PAYMENT_SUCCESS',
                        entityType: 'Payment',
                        entityId: payment.id,
                        newValue: {
                            status: payment_entity_1.PaymentStatus.SUCCESSFUL,
                            mpesaReceipt,
                            amount: payment.amount,
                            phoneNumber: phoneNumber || payment.phoneNumber,
                        },
                    });
                    await auditLogRepo.save(auditLog);
                    if (payment.routerId && payment.customerId) {
                        const session = wifiSessionRepo.create({
                            customerId: payment.customerId,
                            packageId: payment.packageId,
                            routerId: payment.routerId,
                            businessId: payment.businessId,
                            status: wifi_session_entity_1.WifiSessionStatus.PENDING_ACTIVATION,
                        });
                        await wifiSessionRepo.save(session);
                        this.logger.log(`WiFi session created for payment ${payment.id}, session ${session.id}`);
                    }
                    const reconciliation = reconciliationRepo.create({
                        paymentId: payment.id,
                        checkoutRequestId: CheckoutRequestID,
                        reconciliationStatus: 'MATCHED',
                        reconciledAt: new Date(),
                    });
                    await reconciliationRepo.save(reconciliation);
                }
                else {
                    payment.status =
                        ResultCode === 1032
                            ? payment_entity_1.PaymentStatus.CANCELLED
                            : payment_entity_1.PaymentStatus.FAILED;
                    payment.callbackPayload = callback;
                    await paymentRepo.save(payment);
                    const auditLog = auditLogRepo.create({
                        businessId: payment.businessId,
                        action: `MPESA_PAYMENT_${payment.status}`,
                        entityType: 'Payment',
                        entityId: payment.id,
                        newValue: {
                            status: payment.status,
                            resultCode: ResultCode,
                            resultDescription: ResultDesc,
                        },
                    });
                    await auditLogRepo.save(auditLog);
                    const reconciliation = reconciliationRepo.create({
                        paymentId: payment.id,
                        checkoutRequestId: CheckoutRequestID,
                        reconciliationStatus: 'MISMATCH',
                        reconciledAt: new Date(),
                    });
                    await reconciliationRepo.save(reconciliation);
                }
                callbackRecord.processed = true;
                callbackRecord.processedAt = new Date();
                await callbackRepo.save(callbackRecord);
            });
            this.logger.log(`Callback processed for CheckoutRequestID: ${CheckoutRequestID}, ResultCode: ${ResultCode}`);
            return { success: true };
        }
        catch (error) {
            this.logger.error(`Callback handling error: ${error.message}`, error.stack);
            return { success: true, message: 'Internal processing error' };
        }
    }
    async verifyTransactionStatus(checkoutRequestId) {
        const accessToken = await this.getAccessToken();
        const timestamp = this.getTimestamp();
        const password = this.generateMpesaPassword(this.mpesaConfig.shortCode, this.mpesaConfig.passkey, timestamp);
        const requestPayload = {
            BusinessShortCode: this.mpesaConfig.shortCode,
            Password: password,
            Timestamp: timestamp,
            CheckoutRequestID: checkoutRequestId,
        };
        try {
            const response = await this.axiosInstance.post(MpesaService_1.STK_QUERY_PATH, requestPayload, {
                headers: { Authorization: `Bearer ${accessToken}` },
            });
            const result = response.data;
            const payment = await this.paymentRepo.findOne({
                where: { checkoutRequestId },
            });
            if (payment) {
                const parsedCode = parseInt(result.ResultCode, 10);
                payment.resultCode = (isNaN(parsedCode) ? undefined : parsedCode);
                payment.resultDescription = (result.ResultDesc || undefined);
                if (result.ResultCode === '0') {
                    if (payment.status !== payment_entity_1.PaymentStatus.SUCCESSFUL) {
                        payment.status = payment_entity_1.PaymentStatus.SUCCESSFUL;
                    }
                }
                else if (result.ResultCode === '1032') {
                    payment.status = payment_entity_1.PaymentStatus.CANCELLED;
                }
                else if (result.ResultCode && result.ResultCode !== '0') {
                    payment.status = payment_entity_1.PaymentStatus.FAILED;
                }
                await this.paymentRepo.save(payment);
            }
            return result;
        }
        catch (error) {
            const errorMessage = error.response?.data?.errorMessage ||
                error.response?.data?.ResponseDescription ||
                error.message;
            this.logger.error(`Transaction status verification failed for ${checkoutRequestId}: ${errorMessage}`);
            throw mpesa_exception_1.MpesaException.reconciliationFailed(errorMessage);
        }
    }
    async reconcilePayment(paymentId) {
        const payment = await this.paymentRepo.findOne({ where: { id: paymentId } });
        if (!payment) {
            throw mpesa_exception_1.MpesaException.paymentNotFound(paymentId);
        }
        this.logger.log(`Reconciling payment ${paymentId} (${payment.internalReference})`);
        let reconciliationStatus = 'UNKNOWN';
        const existingCallback = payment.checkoutRequestId
            ? await this.mpesaCallbackRepo.findOne({
                where: { checkoutRequestId: payment.checkoutRequestId, processed: false },
                order: { createdAt: 'DESC' },
            })
            : null;
        if (existingCallback) {
            this.logger.log(`Found unprocessed callback for ${payment.checkoutRequestId}, processing...`);
            reconciliationStatus = 'CALLBACK_FOUND';
            payment.mpesaCallbackReceived = true;
            payment.resultCode = existingCallback.resultCode;
            payment.resultDescription = existingCallback.resultDescription;
            if (existingCallback.resultCode === 0) {
                payment.status = payment_entity_1.PaymentStatus.SUCCESSFUL;
                payment.mpesaReceipt = existingCallback.mpesaReceipt;
                payment.reconciled = true;
                payment.reconciledAt = new Date();
                reconciliationStatus = 'MATCHED';
                if (existingCallback.mpesaReceipt) {
                    const receiptNumber = (0, id_generator_util_1.generateReceiptNumber)();
                    const receipt = this.receiptRepo.create({
                        businessId: payment.businessId,
                        paymentId: payment.id,
                        customerId: payment.customerId,
                        receiptNumber,
                        amount: payment.amount,
                        paymentMethod: 'MPESA',
                        mpesaReceipt: existingCallback.mpesaReceipt,
                    });
                    await this.receiptRepo.save(receipt);
                }
                if (payment.routerId && payment.customerId) {
                    const session = this.wifiSessionRepo.create({
                        customerId: payment.customerId,
                        packageId: payment.packageId,
                        routerId: payment.routerId,
                        businessId: payment.businessId,
                        status: wifi_session_entity_1.WifiSessionStatus.PENDING_ACTIVATION,
                    });
                    await this.wifiSessionRepo.save(session);
                }
                existingCallback.processed = true;
                existingCallback.processedAt = new Date();
                await this.mpesaCallbackRepo.save(existingCallback);
            }
            else {
                payment.status =
                    existingCallback.resultCode === 1032
                        ? payment_entity_1.PaymentStatus.CANCELLED
                        : payment_entity_1.PaymentStatus.FAILED;
                reconciliationStatus = 'MISMATCH';
                existingCallback.processed = true;
                existingCallback.processedAt = new Date();
                await this.mpesaCallbackRepo.save(existingCallback);
            }
            await this.paymentRepo.save(payment);
        }
        else if (payment.checkoutRequestId) {
            try {
                await this.verifyTransactionStatus(payment.checkoutRequestId);
                reconciliationStatus = 'QUERIED_SAFARICOM';
            }
            catch (err) {
                reconciliationStatus = 'QUERY_FAILED';
            }
        }
        if (payment.status === payment_entity_1.PaymentStatus.CREATED ||
            payment.status === payment_entity_1.PaymentStatus.STK_SENT) {
            const createdAt = payment.createdAt.getTime();
            const now = Date.now();
            const timeoutMs = this.configService.getNumber('MPESA_STK_TIMEOUT', 30) * 1000;
            if (now - createdAt > timeoutMs) {
                payment.status = payment_entity_1.PaymentStatus.TIMED_OUT;
                await this.paymentRepo.save(payment);
                reconciliationStatus = 'TIMED_OUT';
            }
        }
        const reconciliation = this.mpesaReconciliationRepo.create({
            paymentId: payment.id,
            checkoutRequestId: payment.checkoutRequestId || 'UNKNOWN',
            reconciliationStatus,
            reconciledAt: new Date(),
        });
        await this.mpesaReconciliationRepo.save(reconciliation);
        return {
            paymentId: payment.id,
            status: payment.status,
            reconciliationStatus,
        };
    }
    async reversePayment(paymentId) {
        const payment = await this.paymentRepo.findOne({ where: { id: paymentId } });
        if (!payment) {
            throw mpesa_exception_1.MpesaException.paymentNotFound(paymentId);
        }
        if (payment.status !== payment_entity_1.PaymentStatus.SUCCESSFUL) {
            throw mpesa_exception_1.MpesaException.reversalFailed(`Cannot reverse payment with status: ${payment.status}`);
        }
        const accessToken = await this.getAccessToken();
        const timestamp = this.getTimestamp();
        const password = this.generateMpesaPassword(this.mpesaConfig.shortCode, this.mpesaConfig.passkey, timestamp);
        const requestPayload = {
            Initiator: this.mpesaConfig.shortCode,
            SecurityCredential: password,
            CommandID: 'TransactionReversal',
            TransactionID: payment.mpesaReceipt,
            Amount: Math.round(payment.amount),
            ReceiverParty: this.mpesaConfig.shortCode,
            RecieverIdentifierType: '11',
            ResultURL: this.buildCallbackUrl(),
            QueueTimeOutURL: this.buildCallbackUrl(),
            Remarks: `Reversal for ${payment.internalReference}`,
            Occasion: '',
        };
        try {
            const response = await this.axiosInstance.post(MpesaService_1.REVERSAL_PATH, requestPayload, {
                headers: { Authorization: `Bearer ${accessToken}` },
            });
            const result = response.data;
            payment.status = payment_entity_1.PaymentStatus.REVERSED;
            payment.notes = `Reversal initiated: ${result.ConversationID}`;
            await this.paymentRepo.save(payment);
            this.logger.log(`Reversal initiated for payment ${paymentId}: ${result.ConversationID}`);
            return result;
        }
        catch (error) {
            const errorMessage = error.response?.data?.errorMessage ||
                error.response?.data?.ResponseDescription ||
                error.message;
            this.logger.error(`Reversal failed for payment ${paymentId}: ${errorMessage}`);
            throw mpesa_exception_1.MpesaException.reversalFailed(errorMessage);
        }
    }
    async validateMpesaConfig() {
        const issues = [];
        if (!this.mpesaConfig.consumerKey) {
            issues.push('Consumer key is not configured');
        }
        if (!this.mpesaConfig.consumerSecret) {
            issues.push('Consumer secret is not configured');
        }
        if (!this.mpesaConfig.passkey) {
            issues.push('Passkey is not configured');
        }
        if (!this.mpesaConfig.shortCode) {
            issues.push('Short code is not configured');
        }
        if (issues.length > 0) {
            return {
                success: false,
                message: `Configuration issues: ${issues.join('; ')}`,
            };
        }
        try {
            const token = await this.getAccessToken();
            if (token) {
                return {
                    success: true,
                    message: `M-Pesa configuration valid. Environment: ${this.mpesaConfig.environment || 'sandbox'}. Shortcode: ***${this.mpesaConfig.shortCode.slice(-3)}`,
                };
            }
            return { success: false, message: 'Could not obtain access token' };
        }
        catch (error) {
            return {
                success: false,
                message: `Access token test failed: ${error.message}`,
            };
        }
    }
    async getAccountBalance() {
        const accessToken = await this.getAccessToken();
        const timestamp = this.getTimestamp();
        const password = this.generateMpesaPassword(this.mpesaConfig.shortCode, this.mpesaConfig.passkey, timestamp);
        const requestPayload = {
            Initiator: this.mpesaConfig.shortCode,
            SecurityCredential: password,
            CommandID: 'AccountBalance',
            PartyA: this.mpesaConfig.shortCode,
            IdentifierType: '4',
            Remarks: 'Account balance query',
            QueueTimeOutURL: this.buildCallbackUrl(),
            ResultURL: this.buildCallbackUrl(),
        };
        try {
            const response = await this.axiosInstance.post(MpesaService_1.ACCOUNT_BALANCE_PATH, requestPayload, {
                headers: { Authorization: `Bearer ${accessToken}` },
            });
            this.logger.log('Account balance query successful');
            return response.data;
        }
        catch (error) {
            const errorMessage = error.response?.data?.errorMessage ||
                error.response?.data?.ResponseDescription ||
                error.message;
            this.logger.error(`Account balance query failed: ${errorMessage}`);
            throw mpesa_exception_1.MpesaException.configurationError(errorMessage);
        }
    }
    generateMpesaPassword(shortCode, passkey, timestamp) {
        return Buffer.from(`${shortCode}${passkey}${timestamp}`).toString('base64');
    }
    buildCallbackUrl() {
        const configuredUrl = this.mpesaConfig.callbackUrl;
        if (configuredUrl) {
            return configuredUrl;
        }
        const apiBaseUrl = this.configService.get('API_BASE_URL', 'http://localhost:3000');
        return `${apiBaseUrl}/mpesa/callback`;
    }
    getTimestamp() {
        const now = new Date();
        const year = now.getFullYear().toString();
        const month = String(now.getMonth() + 1).padStart(2, '0');
        const day = String(now.getDate()).padStart(2, '0');
        const hours = String(now.getHours()).padStart(2, '0');
        const minutes = String(now.getMinutes()).padStart(2, '0');
        const seconds = String(now.getSeconds()).padStart(2, '0');
        return `${year}${month}${day}${hours}${minutes}${seconds}`;
    }
};
exports.MpesaService = MpesaService;
MpesaService.ACCESS_TOKEN_CACHE_KEY = 'mpesa:access_token';
MpesaService.TOKEN_EXPIRY_BUFFER_SECONDS = 50;
MpesaService.STK_PUSH_PATH = '/mpesa/stkpush/v1/processrequest';
MpesaService.STK_QUERY_PATH = '/mpesa/stkpushquery/v1/query';
MpesaService.REVERSAL_PATH = '/mpesa/reversal/v1/request';
MpesaService.ACCOUNT_BALANCE_PATH = '/mpesa/accountbalance/v1/query';
exports.MpesaService = MpesaService = MpesaService_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)(system_settings_entity_1.SystemSettings)),
    __param(5, (0, typeorm_1.InjectRepository)(business_settings_entity_1.BusinessSettings)),
    __param(6, (0, typeorm_1.InjectRepository)(customer_entity_1.Customer)),
    __param(7, (0, typeorm_1.InjectRepository)(wifi_session_entity_1.WifiSession)),
    __param(8, (0, typeorm_1.InjectRepository)(audit_log_entity_1.AuditLog)),
    __param(9, (0, typeorm_1.InjectRepository)(receipt_entity_1.Receipt)),
    __param(10, (0, typeorm_1.InjectRepository)(transaction_entity_1.Transaction)),
    __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,
        config_service_1.ConfigService,
        typeorm_2.DataSource])
], MpesaService);
