import db from "../models/index.js";
import PsbService from "../thirdParties/psbService.js";
import { v4 as uuidv4 } from "uuid";
import { Op } from "sequelize";
import { calculateSaaSFee } from "../utils/feeCalculator.js";
import MarginCalculator from "../utils/marginCalculator.js";
import PricingHelper from "../utils/pricingHelper.js";
import RateService from "../services/rateService.js";
import { issueOtp, verifyOtp } from "../utils/otpService.js";
import { logAudit } from "../utils/auditLogger.js";
import { isCurrencyActive } from "../utils/currencyHelper.js";
import { adjustMerchantWalletBalance } from "../utils/merchantWalletBalance.js";

const { Merchant, Customer, Transaction, SystemProfit, MerchantIpWhitelist, ExchangeRate, ServicePricing, KybApplication, MerchantWalletBalance, Currency, sequelize } = db;

class MerchantController {
  /**
   * Get merchant details
   */
  static async getMerchantDetails(req, res) {
    try {
      const merchant = req.merchant;

      if (!merchant) {
        return res.status(404).json({ success: false, message: "Merchant not found" });
      }

      const kybApplication = await KybApplication.findOne({
        where: { merchantId: merchant.id },
        attributes: ["status"],
      });

      return res.status(200).json({
        success: true,
        data: {
          ...merchant.toJSON(),
          kybStatus: kybApplication ? kybApplication.status : "Not Started",
        },
      });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * List all customers under this merchant
   */
  static async viewCustomerBalances(req, res) {
    try {
      const merchantId = req.merchant.id;
      const customers = await Customer.findAll({
        where: { merchantId },
        attributes: ["id", "fullName", "email", "accountNumber", "balance", "status"],
      });

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

  /**
   * Merchant initiates a payout from their own wallet
   */
  static async initiatePayout(req, res) {
    const { amount, accountNumber, bankCode, accountName, narration, currency = "NGN" } = req.body;
    const merchantId = req.merchant.id;
    const numAmount = parseFloat(amount);

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

    const transactionReference = `MCH-PAY-${uuidv4().slice(0, 8)}`;
    let totalDebit = 0;
    let providerFee = 0;
    let customerFee = 0;
    let feeProfit = 0;
    let marginProfit = 0;
    let platformRate = 1;
    let liveRate = 1;
    let stampDuty = 0;
    let businessName = "";

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

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

      businessName = merchant.businessName;

      // 1. Fetch Service Pricing
      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;

        // Total debit in NGN (converted principal + converted customer fee)
        totalDebit = (numAmount + customerFee) * platformRate;
      } else {
        // NGN Payout
        totalDebit = numAmount + customerFee;
      }

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

      if (parseFloat(merchant.balance) < totalDebit) {
        throw new Error(`Insufficient funds (Total needed: ₦${totalDebit.toFixed(2)} incl. fees)`);
      }

      // Deduct balance
      await merchant.decrement("balance", { by: totalDebit, transaction: dbTransaction });

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

      await dbTransaction.commit();
    } catch (error) {
      await dbTransaction.rollback();
      console.error("❌ 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 || "Merchant Payout", country: "NG" },
        customer: {
          account: {
            number: accountNumber,
            bank: bankCode,
            name: accountName,
            senderaccountnumber: "1100691259",
            sendername: `${businessName}(Airpero)`,
          }
        },
        merchant: { isFee: false, merchantFeeAccount: "", merchantFeeAmount: "" },
        transactionType: "OTHER_BANKS",
        narration: narration || `Payout by ${businessName}`,
      }, "AIRPERO");
    } catch (psbError) {
      console.error("❌ 9PSB Merchant Payout 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
        await SystemProfit.create({
          amount: feeProfit + marginProfit, // Total Profit
          feeProfit,
          marginProfit,
          currency: "NGN", // Profits are recorded in NGN
          providerFee,
          customerFee,
          source: "payout_fee",
          serviceCategory: "payout",
          transactionId: transactionReference,
          merchantId,
          metadata: {
            liveRate,
            platformRate,
            payoutCurrency: currency,
            stampDuty
          }
        }, { transaction: successTx });

        await successTx.commit();

        return res.status(200).json({
          success: true,
          message: "Payout completed successfully",
          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 payout transaction:", finalizeError.message);
        return res.status(200).json({
          success: true,
          message: "Payout completed successfully (DB sync pending)",
          data: {
            fee: customerFee,
            transaction_id: transactionReference,
          }
        });
      }
    } else {
      // Refund the merchant
      const refundError = psbErrorMsg || psbResponse?.message || "9PSB Payout Failed";
      const refundTx = await sequelize.transaction();
      try {
        const merchant = await Merchant.findByPk(merchantId, { transaction: refundTx, lock: true });
        if (merchant) {
          await merchant.increment("balance", { by: totalDebit, 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 merchant after payout failure:", refundTxError.message);
      }

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

  /**
   * Get aggregated financial stats for the merchant dashboard
   */
  static async getMerchantStats(req, res) {
    try {
      const merchantId = req.merchant.id;

      // 1. Total Funds Available (Across all customers)
      const totalCustomerFunds = await Customer.sum("balance", { where: { merchantId } }) || 0;

      // 2. Transaction Totals (Inflow vs Outflow)
      const transactions = await Transaction.findAll({ where: { merchantId } });

      let totalInflow = 0;
      let totalOutflow = 0;

      transactions.forEach(tx => {
        const amt = parseFloat(tx.amount);
        if (tx.type === 'credit') totalInflow += amt;
        else if (tx.type === 'debit') totalOutflow += amt;
      });

      // 3. Customer Count
      const customerCount = await Customer.count({ where: { merchantId } });

      return res.status(200).json({
        success: true,
        data: {
          merchantBalance: req.merchant.balance,
          totalCustomerFunds,
          totalInflow,
          totalOutflow,
          customerCount,
          currency: "NGN"
        }
      });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * Get all transactions across all customers for this merchant
   */
  static async getMerchantTransactions(req, res) {
    try {
      const merchantId = req.merchant.id;
      const { period, status } = req.query;

      let whereClause = { merchantId };
      if (status) whereClause.status = status;

      if (period) {
        const now = new Date();
        if (period === "daily") {
          now.setHours(0, 0, 0, 0);
          whereClause.createdAt = { [Op.gte]: now };
        } else if (period === "monthly") {
          now.setDate(1);
          now.setHours(0, 0, 0, 0);
          whereClause.createdAt = { [Op.gte]: now };
        } else if (period === "yearly") {
          now.setMonth(0, 1);
          now.setHours(0, 0, 0, 0);
          whereClause.createdAt = { [Op.gte]: now };
        }
      }

      const transactions = await Transaction.findAll({
        where: whereClause,
        order: [["createdAt", "DESC"]],
        include: [
          { model: Customer, attributes: ['fullName', 'email', 'accountNumber'] }
        ]
      });

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

  /**
   * Get all whitelisted IPs for a merchant
   */
  static async getIpWhitelists(req, res) {
    try {
      const merchantId = req.merchant.id;
      const whitelists = await MerchantIpWhitelist.findAll({
        where: { merchantId },
        order: [["createdAt", "DESC"]]
      });

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

  /**
   * Add a new IP address to the whitelist
   */
  static async addIpWhitelist(req, res) {
    try {
      const merchantId = req.merchant.id;
      const { ipAddress } = req.body;

      if (!ipAddress || ipAddress.trim() === "") {
        return res.status(400).json({ success: false, message: "IP Address is required" });
      }

      // Check if already whitelisted
      const cleanIp = ipAddress.trim();
      const existing = await MerchantIpWhitelist.findOne({
        where: { merchantId, ipAddress: cleanIp }
      });

      if (existing) {
        return res.status(400).json({ success: false, message: "IP Address already whitelisted" });
      }

      const whitelistEntry = await MerchantIpWhitelist.create({
        merchantId,
        ipAddress: cleanIp,
        status: "active"
      });

      return res.status(201).json({
        success: true,
        message: "IP Address whitelisted successfully",
        data: whitelistEntry
      });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * Delete an IP address from the whitelist
   */
  static async deleteIpWhitelist(req, res) {
    try {
      const merchantId = req.merchant.id;
      const { id } = req.params;

      const whitelistEntry = await MerchantIpWhitelist.findOne({
        where: { id, merchantId }
      });

      if (!whitelistEntry) {
        return res.status(404).json({ success: false, message: "IP Whitelist entry not found or not owned by you" });
      }

      await whitelistEntry.destroy();

      return res.status(200).json({
        success: true,
        message: "IP Address removed from whitelist successfully"
      });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * Get all active service pricings (fees) from the system.
   * Can be filtered by serviceName and/or currency.
   */
  static async getServicePricings(req, res) {
    try {
      const { serviceName, currency } = req.query;
      let whereClause = { isActive: true };

      if (serviceName) whereClause.serviceName = serviceName;
      if (currency) {
        whereClause.currency = [currency, "*"]; // Include specific currency and wildcard
      }

      const pricings = await ServicePricing.findAll({
        where: whereClause,
        order: [["serviceName", "ASC"]]
      });

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

  /**
   * Get exchange rates. Performs an instant live sync if the source is not manual,
   * updates the database cache, and returns the platform rates.
   */
  static async getExchangeRates(req, res) {
    try {
      const { baseCurrency = "NGN", targetCurrency } = req.query;
      
      let whereClause = {};
      if (baseCurrency) whereClause.baseCurrency = baseCurrency;
      if (targetCurrency) whereClause.targetCurrency = targetCurrency;

      const exchangeRates = await ExchangeRate.findAll({ where: whereClause });

      const results = [];
      for (const rateRecord of exchangeRates) {
        let liveRate = parseFloat(rateRecord.rate);

        // Instant live check if not manual
        if (rateRecord.rateSource !== "manual") {
          try {
            const fetchedRate = await RateService.getLiveRate(
              rateRecord.baseCurrency,
              rateRecord.targetCurrency,
              rateRecord.rateSource,
              rateRecord.rate
            );

            if (fetchedRate > 0) {
              liveRate = fetchedRate;
              // Update the database record in the background or instantly
              await rateRecord.update({ rate: fetchedRate });
            }
          } catch (err) {
            console.error(`[getExchangeRates] Live rate check failed for ${rateRecord.baseCurrency}/${rateRecord.targetCurrency}:`, err.message);
          }
        }

        // Calculate the platform rate (using MarginCalculator)
        const fxResult = MarginCalculator.calculateFxMargin(1, rateRecord, liveRate);

        results.push({
          id: rateRecord.id,
          baseCurrency: rateRecord.baseCurrency,
          targetCurrency: rateRecord.targetCurrency,
          liveRate,
          platformRate: fxResult.platformRate,
          rateSource: rateRecord.rateSource,
          margin: parseFloat(rateRecord.margin),
          marginOperation: rateRecord.marginOperation,
          updatedAt: rateRecord.updatedAt
        });
      }

      return res.status(200).json({
        success: true,
        data: targetCurrency && results.length > 0 ? results[0] : results
      });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * List all of a merchant's wallet balances — the native NGN balance plus
   * any other currency wallets they've swapped into.
   */
  static async getWallets(req, res) {
    try {
      const merchant = req.merchant;

      const otherWallets = await MerchantWalletBalance.findAll({
        where: { merchantId: merchant.id },
        attributes: ["currency", "balance", "status", "updatedAt"],
        order: [["currency", "ASC"]],
      });

      const data = [
        {
          currency: "NGN",
          balance: parseFloat(merchant.balance),
          status: "active",
          updatedAt: merchant.updatedAt,
        },
        ...otherWallets.map((w) => ({
          currency: w.currency,
          balance: parseFloat(w.balance),
          status: w.status,
          updatedAt: w.updatedAt,
        })),
      ];

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

  /**
   * Swap between the merchant's own wallets using an admin-configured exchange rate.
   * `amount` is denominated in targetCurrency (matches MarginCalculator.calculateFxMargin's
   * existing convention across the codebase) — e.g. { baseCurrency: "NGN", targetCurrency: "USD",
   * amount: 100 } converts NGN into exactly 100 USD, debiting the NGN wallet for the computed cost.
   * A pair is only swappable if an admin has configured an ExchangeRate row for that exact
   * (baseCurrency -> targetCurrency) direction; swapping back requires the reverse pair too.
   */
  static async swapCurrency(req, res) {
    try {
      const merchantId = req.merchant.id;
      const baseCurrency = String(req.body.baseCurrency || "").toUpperCase();
      const targetCurrency = String(req.body.targetCurrency || "").toUpperCase();
      const numAmount = parseFloat(req.body.amount);

      if (baseCurrency === targetCurrency) {
        return res.status(400).json({ success: false, message: "baseCurrency and targetCurrency must be different." });
      }

      for (const code of [baseCurrency, targetCurrency]) {
        // NGN is the platform's native currency and doesn't need a Currency row of its own.
        if (code === "NGN") continue;

        if (!(await isCurrencyActive(code))) {
          return res.status(400).json({ success: false, message: `Currency '${code}' is not available for swapping.` });
        }

        const currencyRecord = await Currency.findOne({ where: { code } });
        if (!currencyRecord || currencyRecord.virtualAccountStatus !== "active") {
          return res.status(400).json({ success: false, message: `Currency '${code}' is not enabled for merchant wallets.` });
        }

        const existingWallet = await MerchantWalletBalance.findOne({ where: { merchantId, currency: code } });
        if (existingWallet && existingWallet.status !== "active") {
          return res.status(400).json({ success: false, message: `Your ${code} wallet is currently inactive.` });
        }
      }

      const rateRecord = await ExchangeRate.findOne({ where: { baseCurrency, targetCurrency } });
      if (!rateRecord) {
        return res.status(400).json({
          success: false,
          message: `No exchange rate configured for ${baseCurrency} → ${targetCurrency}. Ask an admin to add this pair.`,
        });
      }

      // Live-refresh the rate the same way the rates listing endpoint does
      let liveRate = parseFloat(rateRecord.rate);
      if (rateRecord.rateSource !== "manual") {
        try {
          const fetched = await RateService.getLiveRate(baseCurrency, targetCurrency, rateRecord.rateSource, rateRecord.rate);
          if (fetched > 0) {
            liveRate = fetched;
            await rateRecord.update({ rate: fetched });
          }
        } catch (err) {
          console.error(`[swapCurrency] Live rate check failed for ${baseCurrency}/${targetCurrency}, using stored rate:`, err.message);
        }
      }

      const fxResult = MarginCalculator.calculateFxMargin(numAmount, rateRecord, liveRate);
      const baseCost = fxResult.convertedAmount; // amount of baseCurrency required

      const t = await sequelize.transaction();
      try {
        // 1. Debit the base currency wallet
        if (baseCurrency === "NGN") {
          const merchant = await Merchant.findByPk(merchantId, { transaction: t, lock: true });
          if (parseFloat(merchant.balance) < baseCost) {
            throw new Error(`Insufficient NGN balance. Required: ${baseCost.toFixed(2)}, Available: ${merchant.balance}`);
          }
          merchant.balance = parseFloat(merchant.balance) - baseCost;
          await merchant.save({ transaction: t });
        } else {
          await adjustMerchantWalletBalance(merchantId, baseCurrency, -baseCost, t);
        }

        // 2. Credit the target currency wallet
        if (targetCurrency === "NGN") {
          const merchant = await Merchant.findByPk(merchantId, { transaction: t, lock: true });
          merchant.balance = parseFloat(merchant.balance) + numAmount;
          await merchant.save({ transaction: t });
        } else {
          await adjustMerchantWalletBalance(merchantId, targetCurrency, numAmount, t);
        }

        // 3. Log platform profit from the FX margin
        if (fxResult.marginProfit > 0) {
          await SystemProfit.create({
            merchantId,
            transactionId: `SWAP-${uuidv4().slice(0, 12).toUpperCase()}`,
            amount: fxResult.marginProfit,
            feeProfit: 0,
            marginProfit: fxResult.marginProfit,
            currency: baseCurrency,
            source: "wallet_swap",
            serviceCategory: "swap",
            metadata: { baseCurrency, targetCurrency, amount: numAmount, platformRate: fxResult.platformRate },
          }, { transaction: t });
        }

        await t.commit();

        logAudit({
          userId: merchantId,
          userType: "merchant",
          action: "WALLET_SWAP",
          details: { baseCurrency, targetCurrency, amount: numAmount, cost: baseCost, platformRate: fxResult.platformRate },
          req,
        });

        return res.status(200).json({
          success: true,
          message: `Swapped ${baseCost.toFixed(2)} ${baseCurrency} for ${numAmount} ${targetCurrency}.`,
          data: {
            baseCurrency,
            targetCurrency,
            amountReceived: numAmount,
            amountDebited: baseCost,
            platformRate: fxResult.platformRate,
          },
        });
      } catch (innerErr) {
        await t.rollback();
        return res.status(422).json({ success: false, message: innerErr.message });
      }
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * Step 1 of a protected payout — request OTP before funds move.
   * Generates and emails an OTP to the merchant's registered email.
   */
  static async requestPayoutOtp(req, res) {
    try {
      const merchant = req.merchant;
      const { otpId } = await issueOtp(
        merchant.id,
        "merchant",
        "payout",
        merchant.email,
        merchant.businessName
      );

      logAudit({ userId: merchant.id, userType: "merchant", action: "PAYOUT_OTP_REQUESTED", req });

      return res.status(200).json({
        success: true,
        otpId,
        message: "OTP sent to your registered email. Submit the OTP along with payout details to proceed.",
      });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }
}

export default MerchantController;
