/**
 * PaymentServiceFactory
 * Factory class to instantiate and manage payment services
 * Provides centralized access to all payment providers
 */

const KudaBankService = require('./KudaBankService');
const OpayService = require('./OpayService');
const MoniepointService = require('./MoniepointService');
const PalmPayService = require('./PalmPayService');
const BinancePayService = require('./BinancePayService');
const BankTransferService = require('./BankTransferService');
const USSDPaymentService = require('./USSDPaymentService');
const MonoVerificationService = require('./MonoVerificationService');
const KorapayService = require('./KorapayService');
const CryptomusService = require('./CryptomusService');

class PaymentServiceFactory {
  constructor(pool = null) {
    this.pool = pool; // Database connection pool
    this.services = new Map();
    this.initializeServices();
  }

  /**
   * Initialize all payment services
   */
  initializeServices() {
    // Automated services (no pool required)
    this.services.set('kuda', new KudaBankService());
    this.services.set('opay', new OpayService());
    this.services.set('moniepoint', new MoniepointService());
    this.services.set('palmpay', new PalmPayService());
    this.services.set('binance', new BinancePayService());
    this.services.set('korapay', new KorapayService());
    this.services.set('cryptomus', new CryptomusService());

    // Manual/Semi-automated services (require pool)
    this.services.set('bank_transfer', new BankTransferService(this.pool));
    this.services.set('ussd', new USSDPaymentService(this.pool));
    this.services.set('mono', new MonoVerificationService(this.pool));
  }

  /**
   * Get a payment service by code
   * @param {string} code - Payment method code (kuda, opay, etc.)
   * @returns {PaymentServiceInterface}
   */
  getService(code) {
    const service = this.services.get(code.toLowerCase());

    if (!service) {
      throw new Error(`Payment service not found: ${code}`);
    }

    return service;
  }

  /**
   * Get service by payment method ID (from database)
   * @param {number} paymentMethodId
   * @returns {Promise<PaymentServiceInterface>}
   */
  async getServiceByMethodId(paymentMethodId) {
    if (!this.pool) {
      throw new Error('Database pool not configured');
    }

    const [methods] = await this.pool.execute(
      'SELECT code FROM payment_methods WHERE id = ? AND is_active = TRUE',
      [paymentMethodId]
    );

    if (methods.length === 0) {
      throw new Error(`Payment method not found or inactive: ${paymentMethodId}`);
    }

    return this.getService(methods[0].code);
  }

  /**
   * Get all available payment services
   * @returns {Array<{code: string, service: PaymentServiceInterface, config: object}>}
   */
  getAllServices() {
    const allServices = [];

    for (const [code, service] of this.services.entries()) {
      const config = service.getConfig();

      // Skip hidden services
      if (config.isHidden) {
        continue;
      }

      allServices.push({
        code: code,
        service: service,
        config: config
      });
    }

    return allServices;
  }

  /**
   * Get automated payment services only
   * @returns {Array}
   */
  getAutomatedServices() {
    return this.getAllServices().filter(s => s.config.isAutomated);
  }

  /**
   * Get manual payment services only
   * @returns {Array}
   */
  getManualServices() {
    return this.getAllServices().filter(s => !s.config.isAutomated);
  }

  /**
   * Initialize a payment for a specific method
   * @param {string} code - Payment method code
   * @param {number} amount
   * @param {string} reference
   * @param {object} metadata
   * @returns {Promise<object>}
   */
  async initializePayment(code, amount, reference, metadata = {}) {
    const service = this.getService(code);
    return await service.initializePayment(amount, reference, metadata);
  }

  /**
   * Verify a payment
   * @param {string} code - Payment method code
   * @param {string} reference
   * @returns {Promise<object>}
   */
  async verifyPayment(code, reference) {
    const service = this.getService(code);
    return await service.verifyPayment(reference);
  }

  /**
   * Handle webhook for any payment method
   * @param {string} code - Payment method code
   * @param {object} payload
   * @param {object} headers
   * @returns {Promise<object>}
   */
  async handleWebhook(code, payload, headers = {}) {
    const service = this.getService(code);

    // Verify signature first
    const signature = headers['x-webhook-signature'] ||
                      headers['x-mono-signature'] ||
                      headers['signature'] ||
                      headers['sign'];

    if (signature) {
      const payloadString = typeof payload === 'string' ? payload : JSON.stringify(payload);
      const isValid = service.verifyWebhookSignature(payloadString, signature);

      if (!isValid) {
        console.error(`Invalid webhook signature for ${code}`);
        return {
          success: false,
          message: 'Invalid webhook signature'
        };
      }
    }

    // Process webhook
    return await service.handleWebhook(payload, headers);
  }

  /**
   * Get payment service configuration
   * @param {string} code - Payment method code
   * @returns {object}
   */
  getServiceConfig(code) {
    const service = this.getService(code);
    return service.getConfig();
  }

  /**
   * Check if a payment method is available
   * @param {string} code
   * @returns {boolean}
   */
  isServiceAvailable(code) {
    return this.services.has(code.toLowerCase());
  }

  /**
   * Set database pool (for late initialization)
   * @param {object} pool
   */
  setPool(pool) {
    this.pool = pool;

    // Re-initialize services that require pool
    this.services.set('bank_transfer', new BankTransferService(pool));
    this.services.set('ussd', new USSDPaymentService(pool));
    this.services.set('mono', new MonoVerificationService(pool));
  }
}

// Singleton instance
let instance = null;

/**
 * Get singleton instance of PaymentServiceFactory
 * @param {object} pool - Database connection pool
 * @returns {PaymentServiceFactory}
 */
function getPaymentServiceFactory(pool = null) {
  if (!instance) {
    instance = new PaymentServiceFactory(pool);
  } else if (pool && !instance.pool) {
    instance.setPool(pool);
  }

  return instance;
}

module.exports = {
  PaymentServiceFactory,
  getPaymentServiceFactory
};
