// services/mpesaService.js
const axios = require('axios');

/**
 * M-Pesa Payment Service Class
 */
class MpesaService {
  constructor(config) {
    this.config = config;
    this.accessToken = null;
    this.tokenExpiry = 0;
    this.validateConfig();
  }

  /**
   * Validate configuration on initialization
   */
  validateConfig() {
    const required = ['consumerKey', 'consumerSecret', 'passkey', 'shortCode', 'callbackURL'];
    const missing = required.filter(key => !this.config[key]);
    
    if (missing.length > 0) {
      throw new Error(`Missing M-Pesa configuration: ${missing.join(', ')}`);
    }
  }

  /**
   * Get base URL based on environment
   */
  getBaseURL() {
    return this.config.environment === 'production'
      ? 'https://api.safaricom.co.ke'
      : 'https://sandbox.safaricom.co.ke';
  }

  /**
   * Generate and cache access token
   */
  async generateAccessToken() {
    try {
      // Return cached token if still valid (with 60s buffer)
      if (this.accessToken && Date.now() < this.tokenExpiry - 60000) {
        return this.accessToken;
      }

      const auth = Buffer.from(
        `${this.config.consumerKey}:${this.config.consumerSecret}`
      ).toString('base64');

      const response = await axios.get(
        `${this.getBaseURL()}/oauth/v1/generate?grant_type=client_credentials`,
        {
          headers: {
            Authorization: `Basic ${auth}`,
          },
          timeout: 10000,
        }
      );

      this.accessToken = response.data.access_token;
      // M-Pesa tokens expire in 3600 seconds
      this.tokenExpiry = Date.now() + 3600000;

      return this.accessToken;
    } catch (error) {
      console.error('Failed to generate access token:', error.response?.data || error.message);
      throw new Error('Failed to authenticate with M-Pesa API');
    }
  }

  /**
   * Generate timestamp and password for STK push
   */
  generatePassword() {
    const timestamp = new Date()
      .toISOString()
      .replace(/[-T:.Z]/g, '')
      .slice(0, 14);

    const password = Buffer.from(
      `${this.config.shortCode}${this.config.passkey}${timestamp}`
    ).toString('base64');

    return { timestamp, password };
  }

  /**
   * Format phone number to M-Pesa format (254XXXXXXXXX)
   */
  formatPhoneNumber(phoneNumber) {
    // Remove any spaces, dashes, or special characters
    let formatted = phoneNumber.replace(/[\s\-\(\)]/g, '');

    // Handle different formats
    if (formatted.startsWith('+254')) {
      formatted = formatted.substring(1);
    } else if (formatted.startsWith('0')) {
      formatted = '254' + formatted.substring(1);
    } else if (formatted.startsWith('254')) {
      // Already in correct format
    } else if (formatted.startsWith('7') || formatted.startsWith('1')) {
      formatted = '254' + formatted;
    }

    // Validate phone number
    if (!/^254[17]\d{8}$/.test(formatted)) {
      throw new Error('Invalid phone number format. Must be a valid Kenyan number.');
    }

    return formatted;
  }

  /**
   * Validate STK push request
   */
  validateSTKPushRequest(request) {
    if (!request.phoneNumber) {
      throw new Error('Phone number is required');
    }
    if (!request.amount || request.amount <= 0) {
      throw new Error('Valid amount is required');
    }
    if (request.amount < 1) {
      throw new Error('Amount must be at least KES 1');
    }
    if (!request.accountReference) {
      throw new Error('Account reference is required');
    }
    if (!request.transactionDesc) {
      throw new Error('Transaction description is required');
    }
  }

  /**
   * Initiate STK Push (Lipa Na M-Pesa Online)
   */
  async initiateSTKPush(request) {
    try {
      // Validate request
      this.validateSTKPushRequest(request);

      // Get access token
      const accessToken = await this.generateAccessToken();

      // Generate timestamp and password
      const { timestamp, password } = this.generatePassword();

      // Format phone number
      const formattedPhone = this.formatPhoneNumber(request.phoneNumber);

      // Prepare STK push payload
      const payload = {
        BusinessShortCode: this.config.shortCode,
        Password: password,
        Timestamp: timestamp,
        TransactionType: 'CustomerPayBillOnline',
        Amount: Math.round(request.amount), // Ensure integer
        PartyA: formattedPhone,
        PartyB: this.config.shortCode,
        PhoneNumber: formattedPhone,
        CallBackURL: this.config.callbackURL,
        AccountReference: request.accountReference.substring(0, 12), // Max 12 chars
        TransactionDesc: request.transactionDesc.substring(0, 13), // Max 13 chars
      };

      // Make STK push request
      const response = await axios.post(
        `${this.getBaseURL()}/mpesa/stkpush/v1/processrequest`,
        payload,
        {
          headers: {
            'Content-Type': 'application/json',
            Authorization: `Bearer ${accessToken}`,
          },
          timeout: 30000,
        }
      );

      return response.data;
    } catch (error) {
      console.error('STK Push Error:', error.response?.data || error.message);
      
      if (error.response?.data) {
        throw new Error(
          error.response.data.errorMessage || 
          'Failed to initiate M-Pesa payment'
        );
      }
      
      throw error;
    }
  }

  /**
   * Query STK Push transaction status
   */
  async querySTKPushStatus(checkoutRequestID) {
    try {
      const accessToken = await this.generateAccessToken();
      const { timestamp, password } = this.generatePassword();

      const payload = {
        BusinessShortCode: this.config.shortCode,
        Password: password,
        Timestamp: timestamp,
        CheckoutRequestID: checkoutRequestID,
      };

      const response = await axios.post(
        `${this.getBaseURL()}/mpesa/stkpushquery/v1/query`,
        payload,
        {
          headers: {
            'Content-Type': 'application/json',
            Authorization: `Bearer ${accessToken}`,
          },
          timeout: 10000,
        }
      );

      return response.data;
    } catch (error) {
      console.error('Query Error:', error.response?.data || error.message);
      throw new Error('Failed to query transaction status');
    }
  }

  /**
   * Process M-Pesa callback
   */
  extractCallbackData(callbackData) {
    const stkCallback = callbackData.Body?.stkCallback;

    if (!stkCallback) {
      throw new Error('Invalid callback data structure');
    }

    const result = {
      CheckoutRequestID: stkCallback.CheckoutRequestID,
      MerchantRequestID: stkCallback.MerchantRequestID,
      ResultDesc: stkCallback.ResultDesc,
    };

    if (stkCallback.ResultCode === 0) {
      // Payment successful
      result.status = 'Completed';

      // Extract metadata
      if (stkCallback.CallbackMetadata?.Item) {
        const metadata = stkCallback.CallbackMetadata.Item;
        
        metadata.forEach((item) => {
          switch (item.Name) {
            case 'MpesaReceiptNumber':
              result.MpesaReceiptNumber = String(item.Value);
              break;
            case 'TransactionDate':
              result.TransactionDate = String(item.Value);
              break;
            case 'Amount':
              result.Amount = Number(item.Value);
              break;
            case 'PhoneNumber':
              result.PhoneNumber = String(item.Value);
              break;
          }
        });
      }

      return { success: true, data: result };
    } else {
      // Payment failed
      result.status = 'Failed';
      return { success: false, data: result };
    }
  }
}

// Initialize service instance
let mpesaServiceInstance = null;

/**
 * Initialize M-Pesa service (call this once in your app)
 */
const initializeMpesaService = (config) => {
  mpesaServiceInstance = new MpesaService(config);
  return mpesaServiceInstance;
};

/**
 * Get M-Pesa service instance
 */
const getMpesaService = () => {
  if (!mpesaServiceInstance) {
    throw new Error('M-Pesa service not initialized. Call initializeMpesaService() first.');
  }
  return mpesaServiceInstance;
};

// Export service and functions
module.exports = {
  MpesaService,
  initializeMpesaService,
  getMpesaService,
};