// controllers/mpesaController.js
require('dotenv').config();
const { getMpesaService } = require('../services/mpesaService');
const { MpesaPayment, TransactionPayment, Transaction, Orders } = require('../models');
const NodeCache = require('node-cache');

// Enhanced cache with different TTLs
const statusCache = new NodeCache({ 
  stdTTL: 30, // Default 30 seconds
  checkperiod: 60,
  useClones: false
});

// Enhanced rate limiter with timestamp tracking
const apiCallTracker = {
  calls: [], // Array of timestamps
  maxCalls: 4,
  windowMs: 60000, // 1 minute
  minInterval: 15000, // Minimum 15 seconds between calls
};

/**
 * Enhanced rate limit checker
 */
const canMakeApiCall = () => {
  const now = Date.now();
  
  // Remove calls outside the window
  apiCallTracker.calls = apiCallTracker.calls.filter(
    timestamp => now - timestamp < apiCallTracker.windowMs
  );
  
  // Check if we've hit max calls in window
  if (apiCallTracker.calls.length >= apiCallTracker.maxCalls) {
    const oldestCall = apiCallTracker.calls[0];
    const waitTime = apiCallTracker.windowMs - (now - oldestCall);
    return { 
      allowed: false, 
      waitTime: Math.max(waitTime, 0),
      reason: 'max_calls_reached'
    };
  }
  
  // Check minimum interval
  if (apiCallTracker.calls.length > 0) {
    const lastCall = apiCallTracker.calls[apiCallTracker.calls.length - 1];
    const timeSinceLastCall = now - lastCall;
    
    if (timeSinceLastCall < apiCallTracker.minInterval) {
      return { 
        allowed: false, 
        waitTime: apiCallTracker.minInterval - timeSinceLastCall,
        reason: 'min_interval'
      };
    }
  }
  
  return { allowed: true };
};

/**
 * Record an API call
 */
const recordApiCall = () => {
  apiCallTracker.calls.push(Date.now());
};

/**
 * Initiate payment from POS
 * POST /api/mpesa/pay
 */
const initiatePayment = async (req, res) => {
  try {
    const { phoneNumber, amount, orderId, transactionDesc } = req.body;

    // Enhanced validation
    if (!phoneNumber || !amount) {
      return res.status(400).json({
        success: false,
        message: 'Phone number and amount are required',
      });
    }

    // Validate amount range
    if (amount < 1 || amount > 150000) {
      return res.status(400).json({
        success: false,
        message: 'Amount must be between KES 1 and KES 150,000',
      });
    }

    // Check for duplicate pending payment
    if (orderId) {
      const existingPayment = await MpesaPayment.findOne({
        where: {
          orderId,
          status: 'Pending',
        },
      });
      
      if (existingPayment) {
        console.log('⚠️ Duplicate payment attempt for order:', orderId);
        return res.status(409).json({
          success: false,
          message: 'Payment already in progress for this order',
          data: {
            CheckoutRequestID: existingPayment.CheckoutRequestID,
          },
        });
      }
    }

    const mpesaService = getMpesaService();

    // Initiate STK push
    const stkResponse = await mpesaService.initiateSTKPush({
      phoneNumber,
      amount,
      accountReference: orderId || `ORDER${Date.now()}`,
      transactionDesc: transactionDesc || 'POS Payment',
      orderId,
    });

    // Check if request was successful
    if (stkResponse.ResponseCode === '0') {
      // Save payment record to database
      const payment = await MpesaPayment.create({
        CheckoutRequestID: stkResponse.CheckoutRequestID,
        MerchantRequestID: stkResponse.MerchantRequestID,
        PhoneNumber: phoneNumber,
        Amount: amount,
        AccountReference: orderId || `ORDER${Date.now()}`,
        TransactionDesc: transactionDesc || 'POS Payment',
        status: 'Pending',
        orderId,
      });

      console.log('✓ Payment record created:', payment.id);

      return res.status(200).json({
        success: true,
        message: 'Payment request sent. Please check your phone.',
        data: {
          CheckoutRequestID: stkResponse.CheckoutRequestID,
          MerchantRequestID: stkResponse.MerchantRequestID,
          paymentId: payment.id,
        },
      });
    } else {
      return res.status(400).json({
        success: false,
        message: stkResponse.ResponseDescription || 'Payment request failed',
      });
    }
  } catch (error) {
    console.error('❌ Payment initiation error:', error);
    return res.status(500).json({
      success: false,
      message: error.message || 'Payment initiation failed',
    });
  }
};

/**
 * Handle M-Pesa callback with idempotency
 * POST /api/mpesa/callback
 */
const handleCallback = async (req, res) => {
  const startTime = Date.now();
  
  try {
    const callbackData = req.body;

    console.log('📥 M-PESA CALLBACK RECEIVED:', JSON.stringify(callbackData, null, 2));

    // ACKNOWLEDGE SAFARICOM IMMEDIATELY (CRITICAL!)
    res.status(200).json({
      ResultCode: 0,
      ResultDesc: 'Accepted',
    });

    // Process in background (fire and forget)
    setImmediate(async () => {
      try {
        const mpesaService = getMpesaService();
        const { success, data } = mpesaService.extractCallbackData(callbackData);
        
        if (!data.CheckoutRequestID) {
          console.error('❌ Missing CheckoutRequestID in callback');
          return;
        }

        // Find payment with lock to prevent race conditions
        const payment = await MpesaPayment.findOne({
          where: { CheckoutRequestID: data.CheckoutRequestID },
          lock: true, // Pessimistic locking for race condition prevention
        });

        if (!payment) {
          console.error('❌ Payment not found:', data.CheckoutRequestID);
          return;
        }

        // Check if already processed (idempotency check)
        if (payment.status === 'Completed' || payment.status === 'Failed') {
          console.log('⚠️ Callback already processed:', data.CheckoutRequestID, '- Status:', payment.status);
          return;
        }

        // Update payment record with all callback data
        await payment.update({
          ...data,
          status: success ? 'Completed' : 'Failed',
          callbackReceivedAt: new Date(),
        });

        console.log(`✓ Payment ${data.CheckoutRequestID} → ${success ? 'COMPLETED' : 'FAILED'}`);

        // Clear cache for this payment
        statusCache.del(data.CheckoutRequestID);

        // Update associated transaction if exists
        if (payment.orderId) {
          try {
            const transaction = await Transaction.findByPk(payment.orderId);
            
            if (transaction) {
              await transaction.update({
                status: success ? 'completed' : 'failed',
              });
              
              // Update payment reference in transaction
              if (success && data.MpesaReceiptNumber) {
                const transactionPayment = await TransactionPayment.findOne({
                  where: { 
                    transactionId: payment.orderId,
                    paymentMethod: 'mobile_money'
                  }
                });
                
                if (transactionPayment) {
                  await transactionPayment.update({
                    referenceNumber: data.MpesaReceiptNumber
                  });
                  console.log('✓ Transaction payment reference updated');
                }
              }
              
              console.log(`✓ Transaction ${payment.orderId} updated to ${success ? 'completed' : 'failed'}`);
            }
          } catch (txnError) {
            console.error('❌ Failed to update transaction:', txnError.message);
          }
        }

        const processingTime = Date.now() - startTime;
        console.log(`⏱ Callback processed in ${processingTime}ms`);

      } catch (bgError) {
        console.error('❌ Background callback processing error:', bgError);
      }
    });

  } catch (error) {
    console.error('❌ Callback processing error:', error);
    
    // Still try to acknowledge if headers not sent
    if (!res.headersSent) {
      res.status(200).json({
        ResultCode: 0,
        ResultDesc: 'Accepted',
      });
    }
  }
};

/**
 * Query payment status with intelligent caching and rate limiting
 * GET /api/mpesa/status/:checkoutRequestID
 */
const queryPaymentStatus = async (req, res) => {
  try {
    const { checkoutRequestID } = req.params;

    if (!checkoutRequestID) {
      return res.status(400).json({
        success: false,
        message: 'CheckoutRequestID is required',
      });
    }

    // Step 1: Check cache first
    const cachedStatus = statusCache.get(checkoutRequestID);
    if (cachedStatus) {
      console.log('📦 Cache hit:', checkoutRequestID);
      return res.status(200).json({
        success: true,
        data: { ...cachedStatus, cached: true },
      });
    }

    // Step 2: Check database
    const payment = await MpesaPayment.findOne({
      where: { CheckoutRequestID: checkoutRequestID },
    });

    // Step 3: If payment is finalized, return DB status (no API call needed)
    if (payment && (payment.status === 'Completed' || payment.status === 'Failed')) {
      const result = {
        mpesaStatus: { 
          ResultCode: payment.ResultCode || (payment.status === 'Completed' ? '0' : '1'),
          ResultDesc: payment.ResultDesc || payment.status,
          MpesaReceiptNumber: payment.MpesaReceiptNumber,
        },
        localStatus: payment.status,
        payment: payment,
        source: 'database',
        finalized: true,
      };
      
      // Cache finalized statuses for longer (5 minutes)
      statusCache.set(checkoutRequestID, result, 300);
      
      console.log('✓ Finalized status from DB:', checkoutRequestID);
      return res.status(200).json({
        success: true,
        data: result,
      });
    }

    // Step 4: For pending payments, check rate limit before API call
    const rateLimitCheck = canMakeApiCall();
    
    if (!rateLimitCheck.allowed) {
      const waitSeconds = Math.ceil(rateLimitCheck.waitTime / 1000);
      console.log(`⚠️ Rate limit: wait ${waitSeconds}s (${rateLimitCheck.reason})`);
      
      // Return local status with rate limit info
      return res.status(200).json({
        success: true,
        data: {
          mpesaStatus: null,
          localStatus: payment ? payment.status : 'Pending',
          payment: payment,
          rateLimited: true,
          retryAfter: waitSeconds,
          message: `Rate limit reached. Please wait ${waitSeconds} seconds.`,
        },
      });
    }

    // Step 5: Make API call (only for pending payments)
    const mpesaService = getMpesaService();
    let mpesaStatus = null;
    
    try {
      recordApiCall(); // Record this call
      mpesaStatus = await mpesaService.querySTKPushStatus(checkoutRequestID);
      console.log('✓ M-Pesa API query successful:', checkoutRequestID);
      
      // If M-Pesa returns completed/failed, update our database
      if (mpesaStatus?.ResultCode === '0' || 
          (mpesaStatus?.ResultCode && mpesaStatus.ResultCode !== '4999')) {
        
        if (payment) {
          await payment.update({
            ResultCode: mpesaStatus.ResultCode,
            ResultDesc: mpesaStatus.ResultDesc,
            MpesaReceiptNumber: mpesaStatus.MpesaReceiptNumber,
            status: mpesaStatus.ResultCode === '0' ? 'Completed' : 'Failed',
          });
          console.log('✓ Payment status updated from API query');
        }
      }
      
    } catch (error) {
      console.error('✗ M-Pesa API query failed:', error.message);
      
      // Return local status on API error
      return res.status(200).json({
        success: true,
        data: {
          mpesaStatus: null,
          localStatus: payment ? payment.status : 'Pending',
          payment: payment,
          apiError: error.message.includes('Spike arrest') 
            ? 'Rate limited by M-Pesa' 
            : 'API temporarily unavailable',
          source: 'database',
        },
      });
    }

    // Step 6: Prepare and cache result
    const result = {
      mpesaStatus: mpesaStatus,
      localStatus: payment ? payment.status : null,
      payment: payment,
      source: 'mpesa-api',
    };

    // Cache for 30 seconds
    statusCache.set(checkoutRequestID, result);

    return res.status(200).json({
      success: true,
      data: result,
    });
  } catch (error) {
    console.error('❌ Status query error:', error);
    return res.status(500).json({
      success: false,
      message: 'Failed to query payment status',
      error: process.env.NODE_ENV === 'development' ? error.message : undefined,
    });
  }
};

/**
 * Get payment by order ID
 * GET /api/mpesa/payment/:orderId
 */
const getPaymentByOrderId = async (req, res) => {
  try {
    const { orderId } = req.params;

    const payment = await MpesaPayment.findOne({
      where: { orderId },
      order: [['createdAt', 'DESC']],
    });

    if (!payment) {
      return res.status(404).json({
        success: false,
        message: 'Payment not found',
      });
    }

    return res.status(200).json({
      success: true,
      data: payment,
    });
  } catch (error) {
    console.error('❌ Get payment error:', error);
    return res.status(500).json({
      success: false,
      message: 'Failed to retrieve payment',
    });
  }
};

module.exports = {
  initiatePayment,
  handleCallback,
  queryPaymentStatus,
  getPaymentByOrderId,
};