import db from "../models/index.js";
import PsbService from "../thirdParties/psbService.js";
import { v4 as uuidv4 } from "uuid";
import { calculateSaaSFee } from "../utils/feeCalculator.js";
import WebhookService from "../services/webhookService.js";
import MarginCalculator from "../utils/marginCalculator.js";
import PricingHelper from "../utils/pricingHelper.js";
import RateService from "../services/rateService.js";
import ServiceGuard from "../utils/serviceGuard.js";

const { Merchant, Customer, Transaction, ExchangeRate, ServicePricing, SystemProfit, sequelize } = db;

class TransactionController {
  /**
   * Payout from Merchant's account (effectively from customer sub-accounts or merchant funds)
   */
  static async initiatePayout(req, res) {
    const { amount, accountNumber, bankCode, accountName, narration, currency = "NGN" } = req.body;
    const merchantId = req.merchant.id;
    const { customerId } = req.params;

    if (!customerId || !amount || !accountNumber || !bankCode) {
      return res.status(400).json({ success: false, message: "Missing required fields" });
    }

    const transactionReference = `PAYOUT-${uuidv4().slice(0, 8)}`;
    const numAmount = parseFloat(amount);
    let providerFee = 0;
    let customerFee = 0;
    let feeProfit = 0;
    let marginProfit = 0;
    let platformRate = 1;
    let liveRate = 1;
    let totalAmountForCustomer = 0; // in NGN
    let totalFeeForMerchant = 0;    // in NGN
    let stampDuty = 0;
    let customerName = "";

    // Verify customer's access to the virtual account service (allowing payouts) based on KYC
    try {
      await ServiceGuard.checkAccess(customerId, "ngn_virtual_account_service");
    } catch (guardError) {
      return res.status(403).json({ success: false, message: guardError.message });
    }

    // --- PHASE 1: DEDUCT & COMMIT IMMEDIATELY ---
    const dbTransaction = await sequelize.transaction();
    try {
      const customer = await Customer.findByPk(customerId, {
        transaction: dbTransaction,
        lock: true
      });

      if (!customer) {
        throw new Error("Customer not found");
      }

      customerName = customer.fullName;

      // 1. Fetch Service Pricing (using PricingHelper to support subscription overrides)
      const pricing = await PricingHelper.getEffectiveServicePricing(merchantId, "payout", currency, dbTransaction);

      // 2. Calculate Fees
      const pricingResult = MarginCalculator.calculatePricingMargin(numAmount, pricing);
      providerFee = pricingResult.providerFee;
      customerFee = pricingResult.customerFee;
      feeProfit = pricingResult.profit;

      // 3. Handle FX Conversion if currency is not NGN
      if (currency !== "NGN") {
        // Fetch exchange rate config
        const rateConfig = await ExchangeRate.findOne({
          where: { baseCurrency: "NGN", targetCurrency: currency },
          transaction: dbTransaction
        });

        const rateSource = rateConfig ? rateConfig.rateSource : "manual";
        const fallbackRate = rateConfig ? parseFloat(rateConfig.rate) : 0;
        liveRate = await RateService.getLiveRate("NGN", currency, rateSource, fallbackRate);

        const fxResult = MarginCalculator.calculateFxMargin(numAmount, rateConfig, liveRate);
        platformRate = fxResult.platformRate;
        marginProfit = fxResult.marginProfit;

        // Debits converted to NGN
        totalAmountForCustomer = numAmount * platformRate;
        totalFeeForMerchant = customerFee * platformRate;
      } else {
        // NGN Payout
        totalAmountForCustomer = numAmount;
        totalFeeForMerchant = customerFee;
      }

      // 4. Apply NGN Stamp Duty (₦50 for transactions >= ₦10,000)
      if (currency === "NGN" && numAmount >= 10000) {
        stampDuty = 50;
        totalFeeForMerchant += stampDuty;
      }

      if (parseFloat(customer.balance) < totalAmountForCustomer) {
        throw new Error(`Customer has insufficient balance (Needed: ₦${totalAmountForCustomer.toFixed(2)})`);
      }

      const merchant = await Merchant.findByPk(merchantId, {
        transaction: dbTransaction,
        lock: true
      });

      if (!merchant) {
        throw new Error("Merchant not found");
      }

      if (totalFeeForMerchant > 0 && parseFloat(merchant.balance) < totalFeeForMerchant) {
        throw new Error(`Merchant has insufficient balance to cover transaction fees (Needed: ₦${totalFeeForMerchant.toFixed(2)})`);
      }

      // Deduct Customer
      await customer.decrement("balance", { by: totalAmountForCustomer, transaction: dbTransaction });

      // Deduct Merchant if fees > 0
      if (totalFeeForMerchant > 0) {
        await merchant.decrement("balance", { by: totalFeeForMerchant, transaction: dbTransaction });
      }

      // Create pending transaction
      await Transaction.create({
        merchantId,
        customerId,
        transaction_id: transactionReference,
        type: "debit",
        class: "payout",
        amount: numAmount,
        fee: customerFee,
        stampDuty,
        total_amount: totalAmountForCustomer + totalFeeForMerchant,
        status: "pending",
        currency,
        info: `Payout to ${accountName} (${accountNumber}) [Rate: ${platformRate} NGN/${currency}]`,
      }, { transaction: dbTransaction });

      await dbTransaction.commit();
    } catch (error) {
      await dbTransaction.rollback();
      console.error("❌ Customer initiatePayout Phase 1 Error:", error.message);
      return res.status(400).json({ success: false, message: error.message });
    }

    // --- PHASE 2: CALL THIRD PARTY API (OUTSIDE DB TRANSACTION) ---
    let psbResponse;
    let psbErrorMsg = null;

    try {
      psbResponse = await PsbService.walletOtherBanks({
        transaction: { reference: transactionReference },
        order: { amount: numAmount, currency, description: narration || "Payout", country: "NG" },
        customer: {
          account: {
            number: accountNumber,
            bank: bankCode,
            name: accountName,
            senderaccountnumber: "1100691259",
            sendername: `${customerName}(Airpero)`,
          }
        },
        merchant: { isFee: false, merchantFeeAccount: "", merchantFeeAmount: "" },
        transactionType: "OTHER_BANKS",
        narration: narration || `Payout by ${customerName}`,
      }, "AIRPERO");
    } catch (psbError) {
      console.error("❌ 9PSB WalletOtherBanks API Error:", psbError.response?.data || psbError.message);
      psbErrorMsg = psbError.response?.data?.message || psbError.message;
    }

    // --- PHASE 3: FINALIZE (COMMIT SUCCESS OR REFUND) ---
    if (!psbErrorMsg && psbResponse && psbResponse.status === "SUCCESS") {
      const successTx = await sequelize.transaction();
      try {
        await Transaction.update(
          { status: "success" },
          { where: { transaction_id: transactionReference }, transaction: successTx }
        );

        // Log SystemProfit with detailed margins (converted to base NGN)
        await db.SystemProfit.create({
          amount: (feeProfit * platformRate) + marginProfit, // Total Profit
          feeProfit: feeProfit * platformRate,
          marginProfit,
          currency: "NGN", // Profits are recorded in NGN
          providerFee: providerFee * platformRate,
          customerFee: customerFee * platformRate,
          source: "payout_fee",
          serviceCategory: "payout",
          transactionId: transactionReference,
          merchantId,
          customerId,
          metadata: {
            liveRate,
            platformRate,
            payoutCurrency: currency,
            stampDuty
          }
        }, { transaction: successTx });

        await successTx.commit();

        return res.status(200).json({
          status: "SUCCESS",
          responseCode: psbResponse.responseCode,
          message: psbResponse.message || "Transaction Successful",
          data: {
            ...psbResponse.data,
            fee: customerFee,
            transaction_id: psbResponse?.data?.transactionId || psbResponse?.transactionId || psbResponse?.data?.transaction?.id || transactionReference,
          }
        });
      } catch (finalizeError) {
        await successTx.rollback();
        console.error("❌ Failed to finalize successful customer payout transaction:", finalizeError.message);
        return res.status(200).json({
          status: "SUCCESS",
          message: "Payout completed successfully (DB sync pending)",
          data: {
            fee: customerFee,
            transaction_id: transactionReference,
          }
        });
      }
    } else {
      // Refund both Customer and Merchant
      const refundError = psbErrorMsg || psbResponse?.message || "9PSB Payout Failed";
      const refundTx = await sequelize.transaction();
      try {
        const customer = await Customer.findByPk(customerId, { transaction: refundTx, lock: true });
        if (customer) {
          await customer.increment("balance", { by: totalAmountForCustomer, transaction: refundTx });
        }

        const merchant = await Merchant.findByPk(merchantId, { transaction: refundTx, lock: true });
        if (merchant && totalFeeForMerchant > 0) {
          await merchant.increment("balance", { by: totalFeeForMerchant, transaction: refundTx });
        }

        await Transaction.update(
          { status: "failed", info: `Failed: ${refundError}` },
          { where: { transaction_id: transactionReference }, transaction: refundTx }
        );

        await refundTx.commit();
      } catch (refundTxError) {
        await refundTx.rollback();
        console.error("🚨 CRITICAL: Failed to refund customer/merchant after payout failure:", refundTxError.message);
      }

      return res.status(500).json({
        success: false,
        message: refundError,
      });
    }
  }

  /**
   * 9PSB Webhook for Incoming Transfers (Deposits)
   */
  static async handleWebhook(req, res) {
    const payload = req.body || {};
    let logEntry;

    try {
      // --- Phase 1: Log the raw payload immediately ---
      logEntry = await db.WebhookLog.create({
        payload,
        status: "received"
      });

      const rawCode = payload.code ?? payload.responseCode ?? payload.statusCode ?? payload.status ?? "";
      const codeString = rawCode.toString().trim().toUpperCase();
      const isSuccessCode = ["00", "0", "200", "SUCCESS", "SUCCESSFUL", "APPROVED"].includes(codeString);

      if (!isSuccessCode) {
        await logEntry.update({ status: "processed", message: `Skipped: Status code ${rawCode}` });
        return res.status(200).json({ success: true });
      }

      // --- Phase 2: Process the payload ---
      const result = await TransactionController.processWebhookPayload(payload);

      if (result.success) {
        await logEntry.update({ status: "processed" });
      } else {
        await logEntry.update({ status: "failed", message: result.message });
      }

      return res.status(200).json({ success: true, message: result.message });
    } catch (error) {
      console.error("❌ handleWebhook error:", error);
      if (logEntry) {
        await logEntry.update({ status: "failed", message: error.message });
      }
      return res.status(200).json({ success: true });
    }
  }

  /**
   * Core logic for processing 9PSB webhook payload.
   * Extracted to be reusable for manual retries via the Merchant Dashboard.
   */
  static async processWebhookPayload(payload) {
    try {
      const { transaction, customer: pCustomer, order } = payload || {};

      // Flexible extraction for varying 9PSB webhook payload structures
      const accountNumber =
        pCustomer?.account?.number ||
        pCustomer?.accountNumber ||
        payload.accountNumber ||
        payload.destinationAccount ||
        payload.account_number ||
        payload.destinationAccountNumber ||
        payload.virtualAccountNumber;

      const rawAmount = order?.amount ?? payload.amount ?? payload.transactionAmount ?? payload.orderAmount;
      const amount = parseFloat(rawAmount);

      const sessionId =
        transaction?.sessionid ||
        transaction?.id ||
        payload.sessionid ||
        payload.sessionId ||
        payload.reference ||
        payload.transactionReference ||
        order?.reference ||
        `9psb_${accountNumber}_${Date.now()}`;

      const currency = order?.currency || payload.currency || "NGN";

      if (!accountNumber) {
        return { success: false, message: "Missing account number in deposit webhook payload" };
      }

      if (isNaN(amount) || amount <= 0) {
        return { success: false, message: `Invalid deposit amount: ${rawAmount}` };
      }

      let notificationData = null;

      await sequelize.transaction(async (t) => {
        // --- DEDUPLICATION: Use Transaction ID (SessionID) as a unique key ---
        const existingTx = await Transaction.findOne({
          where: { transaction_id: sessionId },
          transaction: t,
          lock: true
        });
        if (existingTx) return;

        // 1. Find the recipient (Customer or Merchant)
        const dbCustomer = await Customer.findOne({
          where: { accountNumber },
          transaction: t,
          lock: true // FOR UPDATE
        });

        if (dbCustomer) {
          // --- Customer Deposit Flow ---
          // 1. Fetch Service Pricing
          const pricing = await PricingHelper.getEffectiveServicePricing(dbCustomer.merchantId, "wallet_deposit", currency, t);

          const pricingResult = MarginCalculator.calculatePricingMargin(amount, pricing);
          const { providerFee, customerFee, profit: feeProfit } = pricingResult;

          // 2. FX Conversion if currency is not NGN
          let platformRate = 1;
          let liveRate = 1;
          let marginProfit = 0;
          let creditAmountBase = amount;
          let customerFeeBase = customerFee;

          if (currency !== "NGN") {
            const rateConfig = await ExchangeRate.findOne({
              where: { baseCurrency: "NGN", targetCurrency: currency },
              transaction: t
            });
            const rateSource = rateConfig ? rateConfig.rateSource : "manual";
            const fallbackRate = rateConfig ? parseFloat(rateConfig.rate) : 0;
            liveRate = await RateService.getLiveRate("NGN", currency, rateSource, fallbackRate);

            const fxResult = MarginCalculator.calculateFxMargin(amount, rateConfig, liveRate);
            platformRate = fxResult.platformRate;
            marginProfit = fxResult.marginProfit;

            creditAmountBase = amount * platformRate;
            customerFeeBase = customerFee * platformRate;
          }

          // Credit Customer
          await dbCustomer.increment("balance", { by: creditAmountBase, transaction: t });

          // Debit Merchant for the customer's deposit fee
          const merchant = await Merchant.findByPk(dbCustomer.merchantId, { transaction: t, lock: true });
          if (merchant && customerFeeBase > 0) {
            await merchant.decrement("balance", { by: customerFeeBase, transaction: t });
          }

          const savedTx = await Transaction.create({
            merchantId: dbCustomer.merchantId,
            customerId: dbCustomer.id,
            transaction_id: sessionId,
            type: "credit",
            class: "deposit",
            amount: creditAmountBase,
            fee: customerFeeBase,
            total_amount: creditAmountBase,
            status: "success",
            currency: "NGN", // Wallet is in NGN
            info: `Customer Deposit via ${accountNumber} (Fee of ${customerFeeBase.toFixed(2)} NGN charged to Merchant)`,
          }, { transaction: t });

          // Log SystemProfit
          await db.SystemProfit.create({
            amount: (feeProfit * platformRate) + marginProfit,
            feeProfit: feeProfit * platformRate,
            marginProfit,
            currency: "NGN",
            providerFee: providerFee * platformRate,
            customerFee: customerFee * platformRate,
            source: "deposit_fee",
            serviceCategory: "deposit",
            transactionId: sessionId,
            merchantId: dbCustomer.merchantId,
            customerId: dbCustomer.id,
            metadata: {
              liveRate,
              platformRate,
              depositCurrency: currency
            }
          }, { transaction: t });

          notificationData = {
            merchantId: dbCustomer.merchantId,
            event: "PAYIN_SUCCESS",
            customerId: dbCustomer.id,
            payload: {
                transactionId: savedTx.transaction_id,
                customerId: dbCustomer.id,
                customerName: dbCustomer.fullName,
                amount: creditAmountBase,
                fee: customerFeeBase,
                total_amount: creditAmountBase,
                currency: "NGN",
                reference: transaction.reference || sessionId,
                senderName: pCustomer?.account?.sendername,
                senderAccountNumber: pCustomer?.account?.senderaccountnumber,
                senderBankName: pCustomer?.account?.senderbankname,
                senderBankCode: pCustomer?.account?.senderbankcode,
                transactionDate: transaction?.date,
            }
          };
        } else {
          // --- Merchant Funding Flow ---
          const dbMerchant = await Merchant.findOne({
            where: { merchantAccountNumber: accountNumber },
            transaction: t,
            lock: true // FOR UPDATE
          });
          if (!dbMerchant) throw new Error("No Customer or Merchant found for account: " + accountNumber);

          // 1. Fetch Service Pricing
          const pricing = await PricingHelper.getEffectiveServicePricing(dbMerchant.id, "wallet_deposit", currency, t);

          const pricingResult = MarginCalculator.calculatePricingMargin(amount, pricing);
          const { providerFee, customerFee, profit: feeProfit } = pricingResult;

          // 2. FX Conversion if currency is not NGN
          let platformRate = 1;
          let liveRate = 1;
          let marginProfit = 0;
          let creditAmountBase = amount;
          let customerFeeBase = customerFee;

          if (currency !== "NGN") {
            const rateConfig = await ExchangeRate.findOne({
              where: { baseCurrency: "NGN", targetCurrency: currency },
              transaction: t
            });
            const rateSource = rateConfig ? rateConfig.rateSource : "manual";
            const fallbackRate = rateConfig ? parseFloat(rateConfig.rate) : 0;
            liveRate = await RateService.getLiveRate("NGN", currency, rateSource, fallbackRate);

            const fxResult = MarginCalculator.calculateFxMargin(amount, rateConfig, liveRate);
            platformRate = fxResult.platformRate;
            marginProfit = fxResult.marginProfit;

            creditAmountBase = amount * platformRate;
            customerFeeBase = customerFee * platformRate;
          }

          // Credit Merchant
          await dbMerchant.increment("balance", { by: creditAmountBase, transaction: t });

          // Debit Merchant for fee
          if (customerFeeBase > 0) {
            await dbMerchant.decrement("balance", { by: customerFeeBase, transaction: t });
          }

          await Transaction.create({
            merchantId: dbMerchant.id,
            transaction_id: sessionId,
            type: "credit",
            class: "deposit",
            amount: creditAmountBase,
            fee: customerFeeBase,
            total_amount: creditAmountBase,
            status: "success",
            currency: "NGN",
            info: `Merchant Wallet Funding via ${accountNumber} (Fee of ${customerFeeBase.toFixed(2)} NGN charged)`,
          }, { transaction: t });

          // Log SystemProfit
          await db.SystemProfit.create({
            amount: (feeProfit * platformRate) + marginProfit,
            feeProfit: feeProfit * platformRate,
            marginProfit,
            currency: "NGN",
            providerFee: providerFee * platformRate,
            customerFee: customerFee * platformRate,
            source: "deposit_fee",
            serviceCategory: "deposit",
            transactionId: sessionId,
            merchantId: dbMerchant.id,
            metadata: {
              liveRate,
              platformRate,
              depositCurrency: currency
            }
          }, { transaction: t });

          notificationData = {
            merchantId: dbMerchant.id,
            event: "WALLET_FUNDED",
            customerId: null,
            payload: {
                transactionId: sessionId,
                amount: creditAmountBase,
                currency: "NGN",
                reference: transaction.reference || sessionId,
                timestamp: new Date().toISOString(),
                senderName: pCustomer?.account?.sendername,
                senderAccountNumber: pCustomer?.account?.senderaccountnumber,
                senderBankName: pCustomer?.account?.senderbankname,
                senderBankCode: pCustomer?.account?.senderbankcode,
                transactionDate: transaction?.date,
            }
          };
        }
      });

      // --- OUTSIDE Transaction Notification ---
      if (notificationData) {
        WebhookService.notifyMerchant(
            notificationData.merchantId,
            notificationData.event,
            notificationData.payload,
            notificationData.customerId
        ).catch(err => console.error("Merchant Notification Error:", err));
      }

      return { success: true, message: "Processed successfully" };
    } catch (error) {
      console.error("❌ processWebhookPayload error:", error);
      return { success: false, message: error.message };
    }
  }


  /**
   * Get all banks
   */
  static async listBanks(req, res) {
    try {
      const banks = await PsbService.getBanks("AIRPERO");
      return res.status(200).json({ success: true, data: banks });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * Account Name Enquiry
   */
  static async resolveAccount(req, res) {
    try {
      const { accountNumber, bankCode } = req.body;
      const senderAccountNumber = "1100025543";
      const merchantID = "AIRPERO";
      const result = await PsbService.otherBanksEnquiry({
        customer: {
          account: {
            number: accountNumber,
            bank: bankCode,
            senderaccountnumber: senderAccountNumber,
          },
        }
      }, merchantID);

      return res.status(200).json({ success: true, data: result });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * Transaction Status Requery & Webhook Reconciliation
   */
  static async requeryTransaction(req, res) {
    try {
      const { transactionId, amount, accountNo, transactionDate } = req.body;
      const merchantId = req.merchant.id;
      
      // 1. Try to find original tx to get data if not provided
      const tx = await Transaction.findOne({ where: { transaction_id: transactionId, merchantId } });
      
      if (!tx) {
        return res.status(404).json({ success: false, message: "Transaction not found or not owned by you" });
      }
      
      const payload = {
        transactionId: transactionId,
        amount: amount || (tx ? Math.round(parseFloat(tx.amount)) : 0),
        transactionType: "OTHER_BANKS",
        transactionDate: transactionDate || tx.createdAt.toISOString().split('T')[0],
        accountNo: accountNo || ""
      };

      const result = await PsbService.walletRequery(payload, "AIRPERO");

      // 2. Reconcile DB status and trigger webhook notification if state changed
      const psbStatus = result?.status?.toString().toUpperCase();

      if (psbStatus === "SUCCESS" || psbStatus === "00") {
        if (tx.status !== "success") {
          await sequelize.transaction(async (t) => {
            await tx.update(
              { status: "success", info: `${tx.info || ''} [Reconciled via Requery API]` },
              { transaction: t }
            );

            await db.SystemProfit.create({
              amount: parseFloat(tx.fee || 0),
              source: "payout_fee",
              transactionId: tx.transaction_id,
              merchantId: tx.merchantId,
              customerId: tx.customerId
            }, { transaction: t });
          });

          // Dispatch payout success webhook to merchant
          WebhookService.notifyMerchant(
            tx.merchantId,
            "PAYOUT_SUCCESS",
            {
              transactionId: tx.transaction_id,
              transaction_id: tx.transaction_id,
              reference: tx.transaction_id,
              customerId: tx.customerId,
              amount: parseFloat(tx.amount),
              fee: parseFloat(tx.fee),
              total_amount: parseFloat(tx.total_amount),
              currency: tx.currency,
              status: "success"
            },
            tx.customerId
          ).catch((err) => console.error(`⚠️ Webhook error on requery success:`, err.message));
        }
      } else if (psbStatus === "FAILED" || psbStatus === "01") {
        if (tx.status === "pending") {
          await sequelize.transaction(async (t) => {
            if (tx.customerId) {
              const customer = await Customer.findByPk(tx.customerId, { transaction: t, lock: true });
              if (customer) {
                await customer.increment("balance", { by: parseFloat(tx.amount), transaction: t });
              }
              const merchant = await Merchant.findByPk(tx.merchantId, { transaction: t, lock: true });
              if (merchant) {
                const merchantRefund = parseFloat(tx.fee || 0) + parseFloat(tx.stampDuty || 0);
                await merchant.increment("balance", { by: merchantRefund, transaction: t });
              }
            } else {
              const merchant = await Merchant.findByPk(tx.merchantId, { transaction: t, lock: true });
              if (merchant) {
                const totalRefund = parseFloat(tx.amount) + parseFloat(tx.fee || 0) + parseFloat(tx.stampDuty || 0);
                await merchant.increment("balance", { by: totalRefund, transaction: t });
              }
            }

            await tx.update(
              { status: "failed", info: `${tx.info || ''} [Reconciled FAILED via Requery API, refunded]` },
              { transaction: t }
            );
          });

          // Dispatch payout failed webhook to merchant
          WebhookService.notifyMerchant(
            tx.merchantId,
            "PAYOUT_FAILED",
            {
              transactionId: tx.transaction_id,
              transaction_id: tx.transaction_id,
              reference: tx.transaction_id,
              customerId: tx.customerId,
              amount: parseFloat(tx.amount),
              fee: parseFloat(tx.fee),
              total_amount: parseFloat(tx.total_amount),
              currency: tx.currency,
              status: "failed",
              reason: result?.message || "Requeried as failed"
            },
            tx.customerId
          ).catch((err) => console.error(`⚠️ Webhook error on requery failure:`, err.message));
        }
      }

      return res.status(200).json({ success: true, data: result });
    } catch (error) {
      console.error("❌ requeryTransaction error:", error);
      return res.status(500).json({ success: false, message: error.message });
    }
  }
}

export default TransactionController;
