import db from "../models/index.js";
import { Op } from "sequelize";
import bcrypt from "bcryptjs";
import jwt from "jsonwebtoken";
import crypto from "crypto";
import { v4 as uuidv4 } from "uuid";
import PsbService from "../thirdParties/psbService.js";
import RateService from "../services/rateService.js";
import PayscribeService from "../thirdParties/payscribeService.js";
import { logAudit } from "../utils/auditLogger.js";
import { clearCurrencyCache } from "../utils/currencyHelper.js";
import { issueOtp, verifyOtp } from "../utils/otpService.js";
import { sendPasswordResetEmail, sendPasswordChangedEmail } from "../utils/emailService.js";

const { Merchant, Transaction, Admin, Setting, Customer, SystemProfit, KycStage, SystemService, KycStageService, Currency, ServicePricing, EmailTemplate, SubscriptionPlan, MerchantSubscription, SubscriptionInvoice, AdminIpWhitelist, ExchangeRate, Card, MerchantServicePricing, sequelize } = db;

class AdminController {
  /**
   * Admin Login
   */
  static async login(req, res) {
    try {
      const { email, password } = req.body;
      const admin = await Admin.findOne({ where: { email } });

      if (!admin) {
        return res.status(401).json({ success: false, message: "Invalid credentials" });
      }

      if (admin.status === "suspended") {
        return res.status(403).json({ success: false, message: "Account blocked: Please reach out to support" });
      }

      if (admin.status !== "active") {
        return res.status(401).json({ success: false, message: "Account is inactive" });
      }

      const isMatch = admin.password.startsWith("$2a$") || admin.password.startsWith("$2b$") 
        ? await bcrypt.compare(password, admin.password)
        : password === admin.password;

      if (!isMatch) {
        logAudit({ userId: admin.id, userType: "admin", action: "ADMIN_LOGIN_FAILED", req });
        return res.status(401).json({ success: false, message: "Invalid credentials" });
      }

      // If 2FA is enabled, do not issue the full token yet
      if (admin.twoFaEnabled) {
        const { otpId } = await issueOtp(
          admin.id,
          "admin",
          "login",
          admin.email,
          admin.fullName
        );

        logAudit({ userId: admin.id, userType: "admin", action: "ADMIN_LOGIN_2FA_INITIATED", req });

        return res.status(200).json({
          success: true,
          requiresOtp: true,
          otpId,
          message: "OTP sent to your registered email address. Please verify to complete login.",
        });
      }

      const token = jwt.sign(
        { id: admin.id, role: admin.role },
        process.env.JWT_SECRET || "airpero_super_secret_key",
        { expiresIn: "1d" }
      );

      logAudit({ userId: admin.id, userType: "admin", action: "ADMIN_LOGIN", req });

      return res.status(200).json({
        success: true,
        message: "Login successful",
        token,
        admin: {
          id: admin.id,
          fullName: admin.fullName,
          email: admin.email,
          role: admin.role,
          twoFaEnabled: admin.twoFaEnabled,
        },
      });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * SuperAdmin Registers a new Merchant.
   */
  static async registerMerchant(req, res) {
    try {
      const { businessName, email, password } = req.body;

      if (!businessName || !email || !password) {
        return res.status(400).json({ success: false, message: "Missing required fields (businessName, email, password)" });
      }

      const existing = await Merchant.findOne({ where: { email } });
      if (existing) {
        return res.status(400).json({ success: false, message: "Merchant with this email already exists" });
      }

      const publicKey = `pk_${uuidv4().replace(/-/g, "")}`;
      const privateKeyPlain = `sk_${uuidv4().replace(/-/g, "")}`;
      const hashedPrivateKey = await bcrypt.hash(privateKeyPlain, 10);
      const hashedPassword = await bcrypt.hash(password, 10);

      const merchant = await Merchant.create({
        businessName,
        email,
        password: hashedPassword,
        publicKey,
        privateKey: hashedPrivateKey,
      });

      const reference = `MCH-FUND-${uuidv4().slice(0, 8)}`;
      const psbAccount = await PsbService.createStaticAccount({
        reference,
        amount: 0,
        description: `Funding account for ${businessName}`,
        name: businessName,
      });

      if (psbAccount && psbAccount.customer?.account?.number) {
        await merchant.update({
          merchantAccountNumber: psbAccount.customer.account.number,
        });
      }

      logAudit({
          userId: req.admin.id,
          userType: "admin",
          action: "REGISTER_MERCHANT",
          details: { merchantId: merchant.id, businessName },
          req
      });

      return res.status(201).json({
        success: true,
        message: "Merchant registered successfully with API Keys",
        data: {
          id: merchant.id,
          businessName: merchant.businessName,
          email: merchant.email,
          merchantAccountNumber: merchant.merchantAccountNumber,
          publicKey: merchant.publicKey,
          privateKey: privateKeyPlain,
        },
      });
    } catch (error) {
      console.error("❌ registerMerchant error:", error);
      return res.status(500).json({
        success: false,
        message: "Failed to register merchant",
        error: error.message,
      });
    }
  }

  /**
   * Get overall platform reports (all merchants)
   */
  static async getPlatformReport(req, res) {
    try {
      const { startDate, endDate } = req.query;

      let whereClause = {};
      if (startDate && endDate) {
        whereClause.createdAt = {
          [Op.between]: [new Date(startDate), new Date(endDate)],
        };
      }

      const transactions = await Transaction.findAll({ where: whereClause });

      let totalVolume = 0;
      let totalDepositFees = 0;
      let totalPayoutFees = 0;

      transactions.forEach((tx) => {
        totalVolume += parseFloat(tx.amount);
        if (tx.class === "deposit") {
          totalDepositFees += parseFloat(tx.fee);
        } else if (tx.class === "payout") {
          totalPayoutFees += parseFloat(tx.fee);
        }
      });

      return res.status(200).json({
        success: true,
        data: {
          totalVolume,
          totalFees: totalDepositFees + totalPayoutFees,
          totalDepositFees,
          totalPayoutFees,
          merchantCount: await Merchant.count(),
          transactionCount: transactions.length,
        },
      });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * Get dynamic fee settings.
   */
  static async getFeeSettings(req, res) {
      try {
          const settings = await Setting.findAll();
          return res.status(200).json({ success: true, data: settings });
      } catch (error) {
          return res.status(500).json({ success: false, message: error.message });
      }
  }

  /**
   * Update dynamic fee settings.
   */
  static async updateFeeSettings(req, res) {
      try {
          const { depositFeePercent, depositFeeCap, payoutFeeFlat } = req.body;

          if (depositFeePercent !== undefined) {
              await Setting.update({ value: depositFeePercent.toString() }, { where: { key: "deposit_fee_percent" } });
              await db.ServicePricing.update(
                { customerPercentage: parseFloat(depositFeePercent) / 100 }, 
                { where: { serviceName: "wallet_deposit" } }
              );
          }
          if (depositFeeCap !== undefined) {
              await Setting.update({ value: depositFeeCap.toString() }, { where: { key: "deposit_fee_cap" } });
              await db.ServicePricing.update(
                { customerMaxCap: parseFloat(depositFeeCap) }, 
                { where: { serviceName: "wallet_deposit" } }
              );
          }
          if (payoutFeeFlat !== undefined) {
              await Setting.update({ value: payoutFeeFlat.toString() }, { where: { key: "payout_fee_flat" } });
              await db.ServicePricing.update(
                { customerFixedFee: parseFloat(payoutFeeFlat) }, 
                { where: { serviceName: "payout" } }
              );
          }

          logAudit({
              userId: req.admin.id,
              userType: "admin",
              action: "UPDATE_GLOBAL_FEES",
              details: req.body,
              req
          });

          return res.status(200).json({ success: true, message: "Global fee settings updated" });
      } catch (error) {
          return res.status(500).json({ success: false, message: error.message });
      }
  }

  /**
   * Update individual Merchant custom fees.
   */
  static async updateMerchantFees(req, res) {
      try {
          const { merchantId } = req.params;
          const {
              customDepositFeePercent, customDepositFeeCap, customPayoutFeeFlat,
              serviceName, currency,
              providerPercentage, providerFixedFee, providerMinCap, providerMaxCap,
              customerPercentage, customerFixedFee, customerMinCap, customerMaxCap,
          } = req.body;

          // If serviceName + currency are given, this is a per-service/per-currency override
          // (MerchantServicePricing), separate from the legacy flat NGN deposit/payout override below.
          if (serviceName && currency) {
              const merchant = await Merchant.findByPk(merchantId);
              if (!merchant) {
                  return res.status(404).json({ success: false, message: "Merchant not found" });
              }

              const [pricing] = await MerchantServicePricing.findOrCreate({
                  where: { merchantId, serviceName, currency },
                  defaults: {
                      providerPercentage, providerFixedFee, providerMinCap, providerMaxCap,
                      customerPercentage, customerFixedFee, customerMinCap, customerMaxCap,
                  },
              });

              const updates = {};
              if (providerPercentage !== undefined) updates.providerPercentage = providerPercentage;
              if (providerFixedFee !== undefined) updates.providerFixedFee = providerFixedFee;
              if (providerMinCap !== undefined) updates.providerMinCap = providerMinCap;
              if (providerMaxCap !== undefined) updates.providerMaxCap = providerMaxCap;
              if (customerPercentage !== undefined) updates.customerPercentage = customerPercentage;
              if (customerFixedFee !== undefined) updates.customerFixedFee = customerFixedFee;
              if (customerMinCap !== undefined) updates.customerMinCap = customerMinCap;
              if (customerMaxCap !== undefined) updates.customerMaxCap = customerMaxCap;
              if (Object.keys(updates).length > 0) {
                  await pricing.update(updates);
              }

              logAudit({
                  userId: req.admin.id,
                  userType: "admin",
                  action: "UPDATE_MERCHANT_SERVICE_PRICING",
                  details: { merchantId, serviceName, currency, ...updates },
                  req
              });

              return res.status(200).json({ success: true, message: "Merchant service pricing updated", data: pricing });
          }

          // Legacy flat NGN deposit/payout fee override (unchanged behavior)
          await Merchant.update({
              customDepositFeePercent,
              customDepositFeeCap,
              customPayoutFeeFlat,
          }, { where: { id: merchantId } });

          logAudit({
              userId: req.admin.id,
              userType: "admin",
              action: "UPDATE_MERCHANT_FEES",
              details: { merchantId, ...req.body },
              req
          });

          return res.status(200).json({ success: true, message: "Merchant custom fees updated" });
      } catch (error) {
          return res.status(500).json({ success: false, message: error.message });
      }
  }

  /**
   * List a merchant's per-service/per-currency pricing overrides.
   */
  static async getMerchantServicePricing(req, res) {
      try {
          const { merchantId } = req.params;
          const overrides = await MerchantServicePricing.findAll({
              where: { merchantId },
              order: [["serviceName", "ASC"], ["currency", "ASC"]],
          });
          return res.status(200).json({ success: true, data: overrides });
      } catch (error) {
          return res.status(500).json({ success: false, message: error.message });
      }
  }

  /**
   * Remove a merchant's per-service/per-currency pricing override, reverting them
   * back to the subscription-plan override or platform default for that service+currency.
   */
  static async deleteMerchantServicePricing(req, res) {
      try {
          const { merchantId, id } = req.params;
          const pricing = await MerchantServicePricing.findOne({ where: { id, merchantId } });
          if (!pricing) {
              return res.status(404).json({ success: false, message: "Merchant service pricing override not found" });
          }

          await pricing.destroy();

          logAudit({
              userId: req.admin.id,
              userType: "admin",
              action: "DELETE_MERCHANT_SERVICE_PRICING",
              details: { merchantId, id, serviceName: pricing.serviceName, currency: pricing.currency },
              req
          });

          return res.status(200).json({ success: true, message: "Merchant service pricing override removed" });
      } catch (error) {
          return res.status(500).json({ success: false, message: error.message });
      }
  }

  /**
   * List all merchants in the system with pagination and search.
   */
  static async getAllMerchants(req, res) {
      try {
          const { limit = 50, page = 1, status, search } = req.query;
          const offset = (page - 1) * limit;

          let whereClause = {};
          if (status) whereClause.status = status;
          if (search) {
              whereClause[Op.or] = [
                  { businessName: { [Op.like]: `%${search}%` } },
                  { email: { [Op.like]: `%${search}%` } }
              ];
          }

          const { count, rows } = await Merchant.findAndCountAll({
              where: whereClause,
              attributes: ["id", "businessName", "email", "merchantAccountNumber", "balance", "status", "createdAt"],
              limit: parseInt(limit),
              offset: parseInt(offset),
              order: [["createdAt", "DESC"]]
          });

          return res.status(200).json({
              success: true,
              total: count,
              pages: Math.ceil(count / limit),
              currentPage: parseInt(page),
              data: rows
          });
      } catch (error) {
          return res.status(500).json({ success: false, message: error.message });
      }
  }

  /**
   * Update Merchant status (Activate, Deactivate, Suspend)
   */
  static async updateMerchantStatus(req, res) {
    try {
        const { merchantId } = req.params;
        const { status } = req.body;

        if (!["active", "inactive", "suspended"].includes(status)) {
            return res.status(400).json({ success: false, message: "Invalid status (active, inactive, suspended)" });
        }

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

        await merchant.update({ status });

        logAudit({
            userId: req.admin.id,
            userType: "admin",
            action: "UPDATE_MERCHANT_STATUS",
            details: { merchantId, status },
            req
        });

        return res.status(200).json({ success: true, message: `Merchant account is now ${status}` });
    } catch (error) {
        return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * Get a full breakdown of a Merchant's profile, customers, and transactions.
   */
  static async getMerchantFullProfile(req, res) {
    try {
        const { merchantId } = req.params;
        const merchant = await Merchant.findByPk(merchantId, {
            attributes: { exclude: ["password", "privateKey"] }
        });

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

        const customerCount = await Customer.count({ where: { merchantId } });
        const transactionCount = await Transaction.count({ where: { merchantId } });
        
        // Latest 10 customers
        const latestCustomers = await Customer.findAll({
            where: { merchantId },
            limit: 10,
            order: [["createdAt", "DESC"]]
        });

        // Latest 10 transactions
        const latestTransactions = await Transaction.findAll({
            where: { merchantId },
            limit: 10,
            order: [["createdAt", "DESC"]],
            include: [{ model: Customer, attributes: ["fullName", "email"] }]
        });

        return res.status(200).json({
            success: true,
            data: {
                merchant,
                stats: {
                    customerCount,
                    transactionCount
                },
                latestCustomers,
                latestTransactions
            }
        });
    } catch (error) {
        return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * Get detailed stats for a specific merchant (Admin View).
   */
  static async getMerchantStatsAdmin(req, res) {
      try {
          const { merchantId } = req.params;
          const merchant = await Merchant.findByPk(merchantId);

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

          const totalCustomerFunds = await Customer.sum("balance", { where: { merchantId } }) || 0;
          const customerCount = await Customer.count({ where: { merchantId } });
          
          const transactions = await Transaction.findAll({ where: { merchantId } });
          let totalInflow = 0;
          let totalOutflow = 0;
          transactions.forEach(tx => {
              if (tx.type === 'credit') totalInflow += parseFloat(tx.amount);
              else if (tx.type === 'debit') totalOutflow += parseFloat(tx.amount);
          });

          return res.status(200).json({
              success: true,
              data: {
                  merchant: {
                      id: merchant.id,
                      businessName: merchant.businessName,
                      balance: merchant.balance
                  },
                  stats: {
                      totalCustomerFunds,
                      customerCount,
                      totalInflow,
                      totalOutflow
                  }
              }
          });
      } catch (error) {
          return res.status(500).json({ success: false, message: error.message });
      }
  }

  /**
   * Get platform-wide profit statistics.
   */
  static async getSystemProfitStats(req, res) {
      try {
          const profits = await SystemProfit.findAll();
          
          let totalProfit = 0;
          let feeProfit = 0;
          let marginProfit = 0;
          let depositFees = 0;
          let payoutFees = 0;

          profits.forEach(p => {
              const amt = parseFloat(p.amount || 0);
              const fp = parseFloat(p.feeProfit || 0);
              const mp = parseFloat(p.marginProfit || 0);
              totalProfit += amt;
              feeProfit += fp;
              marginProfit += mp;
              
              if (p.source === "deposit_fee") depositFees += amt;
              else if (p.source === "payout_fee") payoutFees += amt;
          });

          return res.status(200).json({
              success: true,
              data: {
                  totalProfit,
                  feeProfit,
                  marginProfit,
                  breakdown: {
                      depositFees,
                      payoutFees
                  },
                  profitCount: profits.length
              }
          });
      } catch (error) {
          return res.status(500).json({ success: false, message: error.message });
      }
  }

  /**
   * Get history of platform profit logs.
   */
  static async getSystemProfitHistory(req, res) {
      try {
          const { limit = 50, page = 1 } = req.query;
          const offset = (page - 1) * limit;

          const history = await SystemProfit.findAndCountAll({
              limit: parseInt(limit),
              offset: parseInt(offset),
              order: [["createdAt", "DESC"]],
              include: [
                  { model: Merchant, attributes: ["businessName", "email"] }
              ]
          });

          return res.status(200).json({
              success: true,
              total: history.count,
              pages: Math.ceil(history.count / limit),
              data: history.rows
          });
      } catch (error) {
          return res.status(500).json({ success: false, message: error.message });
      }
  }

  /**
   * Comprehensive Dashboard Metrics for SuperAdmin
   */
  static async getDashboardStats(req, res) {
    try {
      const { startDate, endDate } = req.query;

      let txWhere = {};
      let merchantWhere = {};
      let profitWhere = {};

      if (startDate && endDate) {
        const start = new Date(startDate);
        const end = new Date(endDate);
        end.setHours(23, 59, 59, 999); 

        txWhere.createdAt = { [Op.between]: [start, end] };
        merchantWhere.createdAt = { [Op.between]: [start, end] };
        profitWhere.createdAt = { [Op.between]: [start, end] };
      }

      // --- Merchant Metrics ---
      const todayStart = new Date();
      todayStart.setHours(0, 0, 0, 0);

      const totalMerchants = await Merchant.count();
      const activeMerchants = await Merchant.count({ where: { status: "active" } });
      const joinedTodayCount = await Merchant.count({ where: { createdAt: { [Op.gte]: todayStart } } });
      const newMerchantsInRange = await Merchant.count({ where: merchantWhere });
      const recentMerchants = await Merchant.findAll({
          limit: 5,
          order: [["createdAt", "DESC"]],
          attributes: ["id", "businessName", "email", "status", "createdAt"]
      });

      // --- Transaction Metrics ---
      const totalTransactions = await Transaction.count({ where: txWhere });
      const successfulTransactions = await Transaction.count({ where: { ...txWhere, status: "success" } });
      const failedTransactions = await Transaction.count({ where: { ...txWhere, status: "failed" } });
      const pendingTransactions = await Transaction.count({ where: { ...txWhere, status: "pending" } });

      const totalCreditVolume = await Transaction.sum("amount", { 
        where: { ...txWhere, type: "credit", status: "success" } 
      }) || 0;
      
      const totalDebitVolume = await Transaction.sum("amount", { 
        where: { ...txWhere, type: "debit", status: "success" } 
      }) || 0;

      // --- Customer Metrics ---
      const totalCustomers = await Customer.count();
      const customersJoinedToday = await Customer.count({ where: { createdAt: { [Op.gte]: todayStart } } });

      // --- Fund Metrics (Total money in system) ---
      const totalMerchantBalances = await Merchant.sum("balance") || 0;
      const totalCustomerBalances = await Customer.sum("balance") || 0;
      const totalSystemFunds = parseFloat(totalMerchantBalances) + parseFloat(totalCustomerBalances);

      // --- System Profits ---
      const totalProfit = await SystemProfit.sum("amount", { where: profitWhere }) || 0;

      // --- Trends (Grouping by Day) ---
      const trendData = await Transaction.findAll({
        where: txWhere,
        attributes: [
          [db.sequelize.fn('DATE', db.sequelize.col('createdAt')), 'date'],
          [db.sequelize.fn('COUNT', db.sequelize.col('id')), 'count'],
          [db.sequelize.fn('SUM', db.sequelize.col('amount')), 'totalAmount'],
          'status',
          'type'
        ],
        group: [db.sequelize.fn('DATE', db.sequelize.col('createdAt')), 'status', 'type'],
        order: [[db.sequelize.fn('DATE', db.sequelize.col('createdAt')), 'ASC']]
      });

      return res.status(200).json({
        success: true,
        data: {
          merchants: {
            total: totalMerchants,
            active: activeMerchants,
            joinedToday: joinedTodayCount,
            newInRange: newMerchantsInRange,
            recent: recentMerchants
          },
          transactions: {
            total: totalTransactions,
            success: successfulTransactions,
            failed: failedTransactions,
            pending: pendingTransactions,
            creditVolume: totalCreditVolume,
            debitVolume: totalDebitVolume
          },
          customers: {
              total: totalCustomers,
              joinedToday: customersJoinedToday
          },
          funds: {
              totalSystemFunds,
              totalMerchantBalances,
              totalCustomerBalances
          },
          profit: {
              total: totalProfit
          },
          trends: trendData
        }
      });
    } catch (error) {
      console.error("❌ getDashboardStats error:", error);
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * List all transactions system-wide with pagination and filters (Admin View).
   */
  static async getAllTransactionsAdmin(req, res) {
      try {
          const { 
              limit = 50, 
              page = 1, 
              status, 
              type, 
              class: txClass, 
              merchantId, 
              customerId, 
              startDate, 
              endDate,
              search 
          } = req.query;
          
          const offset = (page - 1) * limit;

          let whereClause = {};

          if (status) whereClause.status = status;
          if (type) whereClause.type = type;
          if (txClass) whereClause.class = txClass;
          if (merchantId) whereClause.merchantId = merchantId;
          if (customerId) whereClause.customerId = customerId;

          if (startDate && endDate) {
              const start = new Date(startDate);
              const end = new Date(endDate);
              end.setHours(23, 59, 59, 999);
              whereClause.createdAt = { [Op.between]: [start, end] };
          }

          if (search) {
              whereClause[Op.or] = [
                  { transaction_id: { [Op.like]: `%${search}%` } },
                  { info: { [Op.like]: `%${search}%` } }
              ];
          }

          const { count, rows } = await Transaction.findAndCountAll({
              where: whereClause,
              limit: parseInt(limit),
              offset: parseInt(offset),
              order: [["createdAt", "DESC"]],
              include: [
                  { model: Merchant, attributes: ["businessName", "email"] },
                  { model: Customer, attributes: ["fullName", "email"] }
              ]
          });

          return res.status(200).json({
              success: true,
              total: count,
              pages: Math.ceil(count / limit),
              currentPage: parseInt(page),
              data: rows
          });
      } catch (error) {
          console.error("❌ getAllTransactionsAdmin error:", error);
          return res.status(500).json({ success: false, message: error.message });
      }
  }

  /**
   * List all customers system-wide with pagination and search.
   */
  static async getAllCustomersAdmin(req, res) {
    try {
        const { limit = 50, page = 1, merchantId, search } = req.query;
        const offset = (page - 1) * limit;

        let whereClause = {};
        if (merchantId) whereClause.merchantId = merchantId;
        if (search) {
            whereClause[Op.or] = [
                { fullName: { [Op.like]: `%${search}%` } },
                { email: { [Op.like]: `%${search}%` } },
                { phoneNumber: { [Op.like]: `%${search}%` } }
            ];
        }

        const { count, rows } = await Customer.findAndCountAll({
            where: whereClause,
            limit: parseInt(limit),
            offset: parseInt(offset),
            order: [["createdAt", "DESC"]],
            include: [{ model: Merchant, attributes: ["businessName", "email"] }]
        });

        return res.status(200).json({
            success: true,
            total: count,
            pages: Math.ceil(count / limit),
            currentPage: parseInt(page),
            data: rows
        });
    } catch (error) {
        return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * Get full details of a customer, including their merchant and recent activity.
   */
  static async getCustomerFullProfile(req, res) {
    try {
        const { customerId } = req.params;
        const customer = await Customer.findByPk(customerId, {
            include: [{ model: Merchant, attributes: ["businessName", "email"] }]
        });

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

        const transactions = await Transaction.findAll({
            where: { customerId },
            limit: 10,
            order: [["createdAt", "DESC"]]
        });

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

  /**
   * Update Customer status (Activate, Deactivate, Block)
   */
  static async updateCustomerStatus(req, res) {
    try {
        const { customerId } = req.params;
        const { status } = req.body;

        if (!["active", "inactive", "suspended"].includes(status)) {
            return res.status(400).json({ success: false, message: "Invalid status (active, inactive, suspended)" });
        }

        const customer = await Customer.findByPk(customerId);
        if (!customer) {
            return res.status(404).json({ success: false, message: "Customer not found" });
        }

        await customer.update({ status });

        logAudit({
            userId: req.admin.id,
            userType: "admin",
            action: "UPDATE_CUSTOMER_STATUS",
            details: { customerId, status },
            req
        });

        return res.status(200).json({ success: true, message: `Customer status updated to ${status}` });
    } catch (error) {
        return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * Get all KYC Stages and their mapped/unlocked system services.
   */
  static async getKycStages(req, res) {
    try {
      const stages = await KycStage.findAll({
        include: [{
          model: SystemService,
          as: "systemServices",
          through: { attributes: ["isActive"] }
        }],
        order: [["sequence", "ASC"]]
      });
      return res.status(200).json({ success: true, data: stages });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * Get all global platform services.
   */
  static async getSystemServices(req, res) {
    try {
      const services = await SystemService.findAll({ order: [["name", "ASC"]] });
      return res.status(200).json({ success: true, data: services });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * Toggle global activation status of a platform service.
   */
  static async toggleGlobalServiceStatus(req, res) {
    try {
      const { serviceId } = req.params;
      const { isActive } = req.body;

      if (typeof isActive !== "boolean") {
        return res.status(400).json({ success: false, message: "isActive (boolean) is required" });
      }

      const service = await SystemService.findByPk(serviceId);
      if (!service) {
        return res.status(404).json({ success: false, message: "System service not found" });
      }

      await service.update({ isActive });

      logAudit({
        userId: req.admin.id,
        userType: "admin",
        action: "TOGGLE_GLOBAL_SERVICE",
        details: { serviceId, isActive },
        req
      });

      return res.status(200).json({ success: true, message: `Service '${service.name}' global status updated to ${isActive}` });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * Toggle activation status of a service for a specific stage.
   */
  static async toggleStageServiceStatus(req, res) {
    try {
      const { stageId, serviceId } = req.params;
      const { isActive } = req.body;

      if (typeof isActive !== "boolean") {
        return res.status(400).json({ success: false, message: "isActive (boolean) is required" });
      }

      const stage = await KycStage.findByPk(stageId);
      if (!stage) {
        return res.status(404).json({ success: false, message: "KYC stage not found" });
      }

      const service = await SystemService.findByPk(serviceId);
      if (!service) {
        return res.status(404).json({ success: false, message: "System service not found" });
      }

      const [mapping] = await KycStageService.findOrCreate({
        where: { stageId, serviceId }
      });

      await mapping.update({ isActive });

      logAudit({
        userId: req.admin.id,
        userType: "admin",
        action: "TOGGLE_STAGE_SERVICE_MAPPING",
        details: { stageId, serviceId, isActive },
        req
      });

      return res.status(200).json({
        success: true,
        message: `Service '${service.name}' status for Stage '${stage.name}' updated to ${isActive}`
      });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * Map service to a KYC stage.
   */
  static async mapServiceToStage(req, res) {
    try {
      const { stageId } = req.params;
      const { serviceId, isActive = true } = req.body;

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

      const stage = await KycStage.findByPk(stageId);
      if (!stage) {
        return res.status(404).json({ success: false, message: "KYC stage not found" });
      }

      const service = await SystemService.findByPk(serviceId);
      if (!service) {
        return res.status(404).json({ success: false, message: "System service not found" });
      }

      const [mapping, created] = await KycStageService.findOrCreate({
        where: { stageId, serviceId },
        defaults: { isActive }
      });

      if (!created) {
        await mapping.update({ isActive });
      }

      logAudit({
        userId: req.admin.id,
        userType: "admin",
        action: "MAP_SERVICE_TO_STAGE",
        details: { stageId, serviceId, isActive },
        req
      });

      return res.status(200).json({
        success: true,
        message: "Service successfully mapped to KYC stage"
      });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }
  /**
   * Create a new Currency
   */
  static async createCurrency(req, res) {
    try {
      const { code, name, symbol, status, virtualAccountStatus } = req.body;
      if (!code || !name) {
        return res.status(400).json({ success: false, message: "Code and Name are required" });
      }

      const existing = await Currency.findOne({ where: { code: code.toUpperCase() } });
      if (existing) {
        return res.status(400).json({ success: false, message: "Currency code already exists" });
      }

      const currency = await Currency.create({
        code: code.toUpperCase(),
        name,
        symbol,
        status: status || "active",
        virtualAccountStatus: virtualAccountStatus === "active" ? "active" : "inactive",
      });

      logAudit({
        userId: req.admin.id,
        userType: "admin",
        action: "CREATE_CURRENCY",
        details: { code, name, virtualAccountStatus: currency.virtualAccountStatus },
        req
      });

      clearCurrencyCache();

      return res.status(201).json({ success: true, message: "Currency created", data: currency });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * Get all Currencies
   */
  static async getAllCurrencies(req, res) {
    try {
      const currencies = await Currency.findAll();
      return res.status(200).json({ success: true, data: currencies });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * Toggle Currency Status
   */
  static async updateCurrencyStatus(req, res) {
    try {
      const { code } = req.params;
      const { status } = req.body;

      if (!["active", "inactive"].includes(status)) {
        return res.status(400).json({ success: false, message: "Invalid status" });
      }

      const currency = await Currency.findOne({ where: { code: code.toUpperCase() } });
      if (!currency) {
        return res.status(404).json({ success: false, message: "Currency not found" });
      }

      await currency.update({ status });

      logAudit({
        userId: req.admin.id,
        userType: "admin",
        action: "UPDATE_CURRENCY_STATUS",
        details: { code, status },
        req
      });

      clearCurrencyCache();

      return res.status(200).json({ success: true, message: `Currency status updated to ${status}` });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * Toggle whether a currency is enabled for merchant virtual accounts/wallets.
   * The daily wallet-currency sync cron (and the one-time run at server startup)
   * picks this up and creates/activates or deactivates merchant wallets accordingly.
   */
  static async updateCurrencyVirtualAccountStatus(req, res) {
    try {
      const { code } = req.params;
      const { virtualAccountStatus } = req.body;

      if (!["active", "inactive"].includes(virtualAccountStatus)) {
        return res.status(400).json({ success: false, message: "Invalid virtualAccountStatus" });
      }

      const currency = await Currency.findOne({ where: { code: code.toUpperCase() } });
      if (!currency) {
        return res.status(404).json({ success: false, message: "Currency not found" });
      }

      if (currency.code === "NGN") {
        return res.status(400).json({ success: false, message: "NGN is the merchant's native balance and isn't managed through the virtual account wallet system." });
      }

      await currency.update({ virtualAccountStatus });

      logAudit({
        userId: req.admin.id,
        userType: "admin",
        action: "UPDATE_CURRENCY_VIRTUAL_ACCOUNT_STATUS",
        details: { code, virtualAccountStatus },
        req
      });

      return res.status(200).json({ success: true, message: `Virtual account status for ${code} updated to ${virtualAccountStatus}` });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  // ============================================
  // SERVICE PRICING MANAGEMENT
  // ============================================

  /**
   * Get all service pricing rules
   */
  static async getAllServicePricing(req, res) {
    try {
      const pricing = await ServicePricing.findAll();
      return res.status(200).json({ success: true, data: pricing });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * Create new service pricing
   */
  static async createServicePricing(req, res) {
    try {
      const {
        serviceName, currency, providerPercentage, providerFixedFee,
        providerMinCap, providerMaxCap, customerPercentage,
        customerFixedFee, customerMinCap, customerMaxCap, isActive
      } = req.body;

      if (!serviceName || !currency) {
        return res.status(400).json({ success: false, message: "serviceName and currency are required" });
      }

      const newPricing = await ServicePricing.create({
        serviceName, currency,
        providerPercentage: providerPercentage || 0,
        providerFixedFee: providerFixedFee || 0,
        providerMinCap: providerMinCap || 0,
        providerMaxCap: providerMaxCap || 999999999.99,
        customerPercentage: customerPercentage || 0,
        customerFixedFee: customerFixedFee || 0,
        customerMinCap: customerMinCap || 0,
        customerMaxCap: customerMaxCap || 999999999.99,
        isActive: isActive !== undefined ? isActive : true
      });

      logAudit({
        userId: req.admin.id,
        userType: "admin",
        action: "CREATE_SERVICE_PRICING",
        details: { serviceName, currency },
        req
      });

      return res.status(201).json({ success: true, message: "Service pricing created", data: newPricing });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * Update existing service pricing
   */
  static async updateServicePricing(req, res) {
    try {
      const { id } = req.params;
      const updates = req.body;

      const pricing = await ServicePricing.findByPk(id);
      if (!pricing) {
        return res.status(404).json({ success: false, message: "Service pricing not found" });
      }

      await pricing.update(updates);

      logAudit({
        userId: req.admin.id,
        userType: "admin",
        action: "UPDATE_SERVICE_PRICING",
        details: { id, updates },
        req
      });

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

  /**
   * Delete a service pricing rule
   */
  static async deleteServicePricing(req, res) {
    try {
      const { id } = req.params;
      const pricing = await ServicePricing.findByPk(id);

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

      await pricing.destroy();

      logAudit({
        userId: req.admin.id,
        userType: "admin",
        action: "DELETE_SERVICE_PRICING",
        details: { id },
        req
      });

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

  // ============================================
  // ADMIN 2FA & PASSWORD RESET
  // ============================================

  /**
   * Verify OTP after a 2FA login challenge.
   */
  static async verifyLoginOtp(req, res) {
    try {
      const { otpId, code } = req.body;
      if (!otpId || !code) {
        return res.status(400).json({ success: false, message: "otpId and code are required" });
      }

      const result = await verifyOtp(otpId, code);
      if (!result.success) {
        return res.status(400).json({ success: false, message: result.message });
      }

      const { data: otpRecord } = result;
      const admin = await Admin.findByPk(otpRecord.userId);

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

      const token = jwt.sign(
        { id: admin.id, role: admin.role },
        process.env.JWT_SECRET || "airpero_super_secret_key",
        { expiresIn: "1d" }
      );

      logAudit({ userId: admin.id, userType: "admin", action: "ADMIN_LOGIN_2FA_SUCCESS", req });

      return res.status(200).json({
        success: true,
        message: "Login successful",
        token,
        admin: { id: admin.id, fullName: admin.fullName, email: admin.email, role: admin.role, twoFaEnabled: admin.twoFaEnabled },
      });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * Toggle 2FA on or off for the authenticated admin.
   */
  static async toggle2fa(req, res) {
    try {
      const admin = req.admin;
      const newState = !admin.twoFaEnabled;
      await admin.update({ twoFaEnabled: newState });

      logAudit({
        userId: admin.id, userType: "admin",
        action: newState ? "ADMIN_2FA_ENABLED" : "ADMIN_2FA_DISABLED", req
      });

      return res.status(200).json({
        success: true,
        message: `Two-factor authentication has been ${newState ? "enabled" : "disabled"}.`,
        twoFaEnabled: newState,
      });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * Request a password reset link via email.
   */
  static async forgotPassword(req, res) {
    try {
      const { email } = req.body;
      if (!email) {
        return res.status(400).json({ success: false, message: "Email is required" });
      }

      const admin = await Admin.findOne({ where: { email } });
      if (admin) {
        const resetToken = crypto.randomBytes(32).toString("hex");
        const hashedToken = crypto.createHash("sha256").update(resetToken).digest("hex");
        const expiresAt = new Date(Date.now() + 30 * 60 * 1000);

        await admin.update({ passwordResetToken: hashedToken, passwordResetExpires: expiresAt });

        const frontendUrl = process.env.FRONTEND_URL || "https://admin.airpero.com";
        const resetUrl = `${frontendUrl}/reset-password?token=${resetToken}&type=admin`;

        await sendPasswordResetEmail(admin.email, resetUrl, admin.fullName);
        logAudit({ userId: admin.id, userType: "admin", action: "ADMIN_PASSWORD_RESET_REQUESTED", req });
      }

      return res.status(200).json({
        success: true,
        message: "If your email is registered, you will receive a password reset link shortly.",
      });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * Complete password reset with token from the email link.
   */
  static async resetPassword(req, res) {
    try {
      const { token, newPassword } = req.body;
      if (!token || !newPassword) {
        return res.status(400).json({ success: false, message: "Token and newPassword are required" });
      }

      const hashedToken = crypto.createHash("sha256").update(token).digest("hex");
      const admin = await Admin.findOne({ where: { passwordResetToken: hashedToken } });

      if (!admin || !admin.passwordResetExpires || new Date() > new Date(admin.passwordResetExpires)) {
        return res.status(400).json({ success: false, message: "Password reset token is invalid or has expired" });
      }

      const hashedPassword = await bcrypt.hash(newPassword, 10);
      await admin.update({
        password: hashedPassword,
        passwordResetToken: null,
        passwordResetExpires: null,
      });

      // Send password changed success email
      sendPasswordChangedEmail(admin.email, admin.fullName).catch(() => {});

      logAudit({ userId: admin.id, userType: "admin", action: "ADMIN_PASSWORD_RESET_SUCCESS", req });

      return res.status(200).json({
        success: true,
        message: "Password has been reset successfully.",
      });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  // ==========================================
  // SUPERADMIN - EMAIL TEMPLATE MANAGEMENT
  // ==========================================

  /**
   * Get all email templates
   * @route GET /admin/email-templates
   * @access SuperAdmin
   */
  static async getEmailTemplates(req, res) {
    try {
      const templates = await EmailTemplate.findAll({
        attributes: ['id', 'slug', 'name', 'subject', 'availableVariables', 'updatedAt']
      });

      return res.status(200).json({
        success: true,
        data: templates
      });
    } catch (error) {
      console.error("Error fetching email templates:", error);
      return res.status(500).json({ success: false, message: "Internal server error" });
    }
  }

  /**
   * Get single email template by slug
   * @route GET /admin/email-templates/:slug
   * @access SuperAdmin
   */
  static async getEmailTemplate(req, res) {
    try {
      const template = await EmailTemplate.findOne({ where: { slug: req.params.slug } });
      if (!template) {
        return res.status(404).json({ success: false, message: "Email template not found" });
      }

      return res.status(200).json({
        success: true,
        data: template
      });
    } catch (error) {
      console.error("Error fetching email template:", error);
      return res.status(500).json({ success: false, message: "Internal server error" });
    }
  }

  /**
   * Update email template
   * @route PATCH /admin/email-templates/:slug
   * @access SuperAdmin
   */
  static async updateEmailTemplate(req, res) {
    const t = await sequelize.transaction();
    try {
      const { subject, bodyHtml } = req.body;
      const { slug } = req.params;

      const template = await EmailTemplate.findOne({ where: { slug }, transaction: t });
      if (!template) {
        await t.rollback();
        return res.status(404).json({ success: false, message: "Email template not found" });
      }

      if (subject) template.subject = subject;
      if (bodyHtml) template.bodyHtml = bodyHtml;

      await template.save({ transaction: t });

      logAudit({
        userId: req.admin.id,
        userType: "admin",
        action: "UPDATE_EMAIL_TEMPLATE",
        details: { slug },
        req
      });

      await t.commit();

      return res.status(200).json({
        success: true,
        message: "Email template updated successfully",
        data: template
      });
    } catch (error) {
      await t.rollback();
      console.error("Error updating email template:", error);
      return res.status(500).json({ success: false, message: "Internal server error" });
    }
  }

  // ==========================================
  // SUPERADMIN - SUBSCRIPTION PLAN MANAGEMENT
  // ==========================================

  /**
   * Create a subscription plan
   * @route POST /admin/subscription-plans
   * @access SuperAdmin
   */
  static async createSubscriptionPlan(req, res) {
    try {
      const { name, description, monthlyPrice, annualDiscountPercentage, currency, servicePricingOverrides, transactionLimit, benefits, includedServices, sortOrder } = req.body;

      if (!name || !monthlyPrice || !currency) {
        return res.status(400).json({ success: false, message: "name, monthlyPrice, and currency are required" });
      }

      const plan = await SubscriptionPlan.create({
        name,
        description,
        monthlyPrice,
        annualDiscountPercentage: annualDiscountPercentage || 0,
        currency,
        servicePricingOverrides: servicePricingOverrides || null,
        transactionLimit: transactionLimit || null,
        benefits: benefits || [],
        includedServices: includedServices || [],
        sortOrder: sortOrder || 0,
        status: "active",
      });

      logAudit({
        userId: req.admin.id,
        userType: "admin",
        action: "SUBSCRIPTION_PLAN_CREATED",
        details: { planName: name, monthlyPrice },
        req,
      });

      return res.status(201).json({
        success: true,
        message: `Subscription plan "${name}" created successfully.`,
        data: plan,
      });
    } catch (error) {
      console.error("Error creating subscription plan:", error);
      return res.status(500).json({ success: false, message: "Internal server error" });
    }
  }

  /**
   * Get all subscription plans (including inactive)
   * @route GET /admin/subscription-plans
   * @access Admin
   */
  static async getAllSubscriptionPlans(req, res) {
    try {
      const plans = await SubscriptionPlan.findAll({
        order: [["sortOrder", "ASC"], ["createdAt", "DESC"]],
      });

      return res.status(200).json({ success: true, data: plans });
    } catch (error) {
      console.error("Error fetching subscription plans:", error);
      return res.status(500).json({ success: false, message: "Internal server error" });
    }
  }

  /**
   * Update a subscription plan
   * @route PATCH /admin/subscription-plans/:id
   * @access SuperAdmin
   */
  static async updateSubscriptionPlan(req, res) {
    try {
      const { id } = req.params;
      const plan = await SubscriptionPlan.findByPk(id);

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

      const allowedFields = ["name", "description", "monthlyPrice", "annualDiscountPercentage", "currency", "servicePricingOverrides", "transactionLimit", "benefits", "includedServices", "sortOrder", "status"];

      for (const field of allowedFields) {
        if (req.body[field] !== undefined) {
          plan[field] = req.body[field];
        }
      }

      await plan.save();

      logAudit({
        userId: req.admin.id,
        userType: "admin",
        action: "SUBSCRIPTION_PLAN_UPDATED",
        details: { planId: id, changes: Object.keys(req.body) },
        req,
      });

      return res.status(200).json({
        success: true,
        message: `Subscription plan "${plan.name}" updated successfully.`,
        data: plan,
      });
    } catch (error) {
      console.error("Error updating subscription plan:", error);
      return res.status(500).json({ success: false, message: "Internal server error" });
    }
  }

  /**
   * Delete (deactivate) a subscription plan
   * @route DELETE /admin/subscription-plans/:id
   * @access SuperAdmin
   */
  static async deleteSubscriptionPlan(req, res) {
    try {
      const { id } = req.params;
      const plan = await SubscriptionPlan.findByPk(id);

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

      plan.status = "inactive";
      await plan.save();

      logAudit({
        userId: req.admin.id,
        userType: "admin",
        action: "SUBSCRIPTION_PLAN_DEACTIVATED",
        details: { planId: id, planName: plan.name },
        req,
      });

      return res.status(200).json({
        success: true,
        message: `Subscription plan "${plan.name}" has been deactivated.`,
      });
    } catch (error) {
      console.error("Error deleting subscription plan:", error);
      return res.status(500).json({ success: false, message: "Internal server error" });
    }
  }

  /**
   * Get subscription dashboard stats
   * @route GET /admin/subscriptions/stats
   * @access Admin
   */
  static async getSubscriptionStats(req, res) {
    try {
      const [totalPlans, activePlans] = await Promise.all([
        SubscriptionPlan.count(),
        SubscriptionPlan.count({ where: { status: "active" } }),
      ]);

      const [totalSubscriptions, activeSubscriptions, cancelledSubscriptions, expiredSubscriptions] = await Promise.all([
        MerchantSubscription.count(),
        MerchantSubscription.count({ where: { status: "active" } }),
        MerchantSubscription.count({ where: { status: "cancelled" } }),
        MerchantSubscription.count({ where: { status: "expired" } }),
      ]);

      const totalRevenue = await SubscriptionInvoice.sum("amount", {
        where: { paymentStatus: "paid" },
      });

      // Top plans by subscriber count
      const topPlans = await MerchantSubscription.findAll({
        attributes: [
          "planId",
          [sequelize.fn("COUNT", sequelize.col("MerchantSubscription.id")), "subscriberCount"],
        ],
        include: [{ model: SubscriptionPlan, as: "plan", attributes: ["name", "monthlyPrice", "currency", "annualDiscountPercentage"] }],
        where: { status: "active" },
        group: ["planId", "plan.id"],
        order: [[sequelize.literal("subscriberCount"), "DESC"]],
        limit: 5,
      });

      return res.status(200).json({
        success: true,
        data: {
          plans: { total: totalPlans, active: activePlans },
          subscriptions: {
            total: totalSubscriptions,
            active: activeSubscriptions,
            cancelled: cancelledSubscriptions,
            expired: expiredSubscriptions,
          },
          revenue: { total: parseFloat(totalRevenue || 0).toFixed(2) },
          topPlans,
        },
      });
    } catch (error) {
      console.error("Error fetching subscription stats:", error);
      return res.status(500).json({ success: false, message: "Internal server error" });
    }
  }

  /**
   * --- IP Address Management (Admin) ---
   */

  /**
   * Add a new Admin IP Whitelist
   */
  static async addIpWhitelist(req, res) {
    try {
        const { ipAddress, description } = req.body;
        
        if (!ipAddress) {
            return res.status(400).json({ success: false, message: "IP Address is required" });
        }

        const existing = await AdminIpWhitelist.findOne({ where: { ipAddress } });
        if (existing) {
            return res.status(400).json({ success: false, message: "IP Address is already whitelisted" });
        }

        const newIp = await AdminIpWhitelist.create({
            ipAddress,
            description,
            addedBy: req.admin.id
        });

        logAudit({
            userId: req.admin.id,
            userType: "admin",
            action: "ADD_ADMIN_IP_WHITELIST",
            details: { ipAddress },
            req
        });

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

  /**
   * Get all Admin IP Whitelists
   */
  static async getIpWhitelists(req, res) {
      try {
          const { limit = 50, page = 1, status } = req.query;
          const offset = (page - 1) * limit;

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

          const ips = await AdminIpWhitelist.findAndCountAll({
              where: whereClause,
              limit: parseInt(limit),
              offset: parseInt(offset),
              order: [["createdAt", "DESC"]],
              include: [{ model: Admin, as: "creator", attributes: ["fullName", "email"] }]
          });

          return res.status(200).json({
              success: true,
              total: ips.count,
              pages: Math.ceil(ips.count / limit),
              currentPage: parseInt(page),
              data: ips.rows
          });
      } catch (error) {
          return res.status(500).json({ success: false, message: error.message });
      }
  }

  /**
   * Update Admin IP Whitelist (e.g. status)
   */
  static async updateIpWhitelist(req, res) {
      try {
          const { id } = req.params;
          const { status, description } = req.body;

          const ip = await AdminIpWhitelist.findByPk(id);
          if (!ip) {
              return res.status(404).json({ success: false, message: "IP Address not found" });
          }

          if (status) ip.status = status;
          if (description !== undefined) ip.description = description;

          await ip.save();

          logAudit({
              userId: req.admin.id,
              userType: "admin",
              action: "UPDATE_ADMIN_IP_WHITELIST",
              details: { id, status, description },
              req
          });

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

  /**
   * Delete Admin IP Whitelist
   */
  static async deleteIpWhitelist(req, res) {
      try {
          const { id } = req.params;
          
          const ip = await AdminIpWhitelist.findByPk(id);
          if (!ip) {
              return res.status(404).json({ success: false, message: "IP Address not found" });
          }

          await ip.destroy();

          logAudit({
              userId: req.admin.id,
              userType: "admin",
              action: "DELETE_ADMIN_IP_WHITELIST",
              details: { id, ipAddress: ip.ipAddress },
              req
          });

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

  // ─────────────────────────────────────────────────────────────────
  // Exchange Rate Management (SuperAdmin)
  // ─────────────────────────────────────────────────────────────────

  /**
   * List all exchange rates.
   * Each record shows the stored rate, the live rate (fetched on-the-fly if not manual), 
   * and the effective platform rate after applying the margin.
   */
  static async getAllExchangeRates(req, res) {
    try {
      const rates = await ExchangeRate.findAll({ order: [["createdAt", "DESC"]] });

      const results = await Promise.all(rates.map(async (r) => {
        let liveRate = parseFloat(r.rate);

        if (r.rateSource !== "manual") {
          try {
            const fetched = await RateService.getLiveRate(r.baseCurrency, r.targetCurrency, r.rateSource, r.rate);
            if (fetched > 0) liveRate = fetched;
          } catch (_) {}
        }

        const margin   = parseFloat(r.margin);
        const effective = r.marginOperation === "add"
          ? liveRate + margin
          : liveRate - margin;

        return {
          id: r.id,
          baseCurrency: r.baseCurrency,
          targetCurrency: r.targetCurrency,
          rateSource: r.rateSource,
          storedRate: parseFloat(r.rate),
          liveRate,
          margin,
          marginOperation: r.marginOperation,
          effectiveRate: parseFloat(effective.toFixed(4)),
          updatedAt: r.updatedAt,
        };
      }));

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

  /**
   * Create a new exchange rate rule.
   *
   * Body:
   *  - baseCurrency    {string}  e.g. "NGN"
   *  - targetCurrency  {string}  e.g. "USD"
   *  - rateSource      {string}  "manual" | "wewire" | "graph" | "nine_psb"
   *  - rate            {number}  The manual/fallback rate (required when rateSource is "manual")
   *  - margin          {number}  Margin amount or percentage to apply on top of the rate
   *  - marginOperation {string}  "add" | "subtract"
   */
  static async createExchangeRate(req, res) {
    try {
      const { baseCurrency, targetCurrency, rateSource = "manual", rate, margin, marginOperation } = req.body;

      if (!baseCurrency || !targetCurrency) {
        return res.status(400).json({ success: false, message: "baseCurrency and targetCurrency are required" });
      }

      if (!margin || !marginOperation) {
        return res.status(400).json({ success: false, message: "margin and marginOperation (add | subtract) are required" });
      }

      if (!["add", "subtract"].includes(marginOperation)) {
        return res.status(400).json({ success: false, message: "marginOperation must be 'add' or 'subtract'" });
      }

      const validSources = ["manual", "wewire", "graph", "nine_psb"];
      if (!validSources.includes(rateSource)) {
        return res.status(400).json({ success: false, message: `rateSource must be one of: ${validSources.join(", ")}` });
      }

      if (rateSource === "manual" && (!rate || parseFloat(rate) <= 0)) {
        return res.status(400).json({ success: false, message: "A valid 'rate' is required when rateSource is 'manual'" });
      }

      // Check for duplicate pair
      const existing = await ExchangeRate.findOne({ where: { baseCurrency, targetCurrency } });
      if (existing) {
        return res.status(400).json({ success: false, message: `Exchange rate for ${baseCurrency}/${targetCurrency} already exists. Use the update endpoint.` });
      }

      // If live source selected, fetch the rate immediately to store it
      let resolvedRate = parseFloat(rate || 0);
      if (rateSource !== "manual") {
        try {
          const live = await RateService.getLiveRate(baseCurrency, targetCurrency, rateSource, resolvedRate);
          if (live > 0) resolvedRate = live;
        } catch (_) {}
      }

      const newRate = await ExchangeRate.create({
        baseCurrency: baseCurrency.toUpperCase(),
        targetCurrency: targetCurrency.toUpperCase(),
        rateSource,
        rate: resolvedRate,
        margin: parseFloat(margin),
        marginOperation,
      });

      const effective = marginOperation === "add"
        ? resolvedRate + parseFloat(margin)
        : resolvedRate - parseFloat(margin);

      logAudit({
        userId: req.admin.id,
        userType: "admin",
        action: "CREATE_EXCHANGE_RATE",
        details: { baseCurrency, targetCurrency, rateSource, rate: resolvedRate, margin, marginOperation },
        req,
      });

      return res.status(201).json({
        success: true,
        message: "Exchange rate created successfully",
        data: {
          id: newRate.id,
          baseCurrency: newRate.baseCurrency,
          targetCurrency: newRate.targetCurrency,
          rateSource: newRate.rateSource,
          storedRate: resolvedRate,
          margin: parseFloat(margin),
          marginOperation,
          effectiveRate: parseFloat(effective.toFixed(4)),
        },
      });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * Update an existing exchange rate rule.
   *
   * Body (all optional — only send what you want to change):
   *  - rateSource      {string}  "manual" | "wewire" | "graph" | "nine_psb"
   *  - rate            {number}  New manual/fallback rate
   *  - margin          {number}  New margin value
   *  - marginOperation {string}  "add" | "subtract"
   */
  static async updateExchangeRate(req, res) {
    try {
      const { id } = req.params;
      const { rateSource, rate, margin, marginOperation } = req.body;

      const record = await ExchangeRate.findByPk(id);
      if (!record) {
        return res.status(404).json({ success: false, message: "Exchange rate not found" });
      }

      if (marginOperation && !["add", "subtract"].includes(marginOperation)) {
        return res.status(400).json({ success: false, message: "marginOperation must be 'add' or 'subtract'" });
      }

      const validSources = ["manual", "wewire", "graph", "nine_psb"];
      if (rateSource && !validSources.includes(rateSource)) {
        return res.status(400).json({ success: false, message: `rateSource must be one of: ${validSources.join(", ")}` });
      }

      const updates = {};
      if (rateSource)       updates.rateSource      = rateSource;
      if (margin !== undefined) updates.margin        = parseFloat(margin);
      if (marginOperation)  updates.marginOperation  = marginOperation;

      // If switching to manual, the new rate must be provided
      const effectiveSource = rateSource || record.rateSource;
      if (effectiveSource === "manual") {
        if (rate !== undefined) updates.rate = parseFloat(rate);
      } else {
        // For a live source, refresh rate immediately
        try {
          const live = await RateService.getLiveRate(
            record.baseCurrency,
            record.targetCurrency,
            effectiveSource,
            parseFloat(rate || record.rate)
          );
          if (live > 0) updates.rate = live;
        } catch (_) {
          if (rate !== undefined) updates.rate = parseFloat(rate);
        }
      }

      await record.update(updates);

      logAudit({
        userId: req.admin.id,
        userType: "admin",
        action: "UPDATE_EXCHANGE_RATE",
        details: { id, ...updates },
        req,
      });

      const finalRate   = parseFloat(record.rate);
      const finalMargin = parseFloat(record.margin);
      const effective   = record.marginOperation === "add"
        ? finalRate + finalMargin
        : finalRate - finalMargin;

      return res.status(200).json({
        success: true,
        message: "Exchange rate updated successfully",
        data: {
          id: record.id,
          baseCurrency: record.baseCurrency,
          targetCurrency: record.targetCurrency,
          rateSource: record.rateSource,
          storedRate: finalRate,
          margin: finalMargin,
          marginOperation: record.marginOperation,
          effectiveRate: parseFloat(effective.toFixed(4)),
        },
      });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * Delete an exchange rate rule.
   */
  static async deleteExchangeRate(req, res) {
    try {
      const { id } = req.params;

      const record = await ExchangeRate.findByPk(id);
      if (!record) {
        return res.status(404).json({ success: false, message: "Exchange rate not found" });
      }

      await record.destroy();

      logAudit({
        userId: req.admin.id,
        userType: "admin",
        action: "DELETE_EXCHANGE_RATE",
        details: { id, baseCurrency: record.baseCurrency, targetCurrency: record.targetCurrency },
        req,
      });

      return res.status(200).json({ success: true, message: `Exchange rate ${record.baseCurrency}/${record.targetCurrency} deleted successfully` });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * Preview the live rate from a third-party source before saving.
   * Useful for the admin to "check" what WeWire/Graph is returning before committing.
   *
   * Query params:
   *  - baseCurrency    e.g. "NGN"
   *  - targetCurrency  e.g. "USD"
   *  - rateSource      "wewire" | "graph" | "nine_psb"
   */
  static async previewLiveRate(req, res) {
    try {
      const { baseCurrency, targetCurrency, rateSource } = req.query;

      if (!baseCurrency || !targetCurrency || !rateSource) {
        return res.status(400).json({ success: false, message: "baseCurrency, targetCurrency, and rateSource are required" });
      }

      if (rateSource === "manual") {
        return res.status(400).json({ success: false, message: "Cannot preview a manual rate — use 'wewire', 'graph', or 'nine_psb'" });
      }

      const live = await RateService.getLiveRate(baseCurrency.toUpperCase(), targetCurrency.toUpperCase(), rateSource, 0);

      return res.status(200).json({
        success: true,
        data: {
          baseCurrency: baseCurrency.toUpperCase(),
          targetCurrency: targetCurrency.toUpperCase(),
          rateSource,
          liveRate: live,
          fetchedAt: new Date(),
        },
      });
    } catch (error) {
      return res.status(500).json({ success: false, message: `Failed to fetch live rate: ${error.message}` });
    }
  }

  // ─────────────────────────────────────────────────────────────────
  // Admin Card Management
  // ─────────────────────────────────────────────────────────────────

  /**
   * List all virtual cards under a specific merchant
   * (across all of the merchant's customers).
   * GET /admin/merchants/:merchantId/cards
   */
  static async getCardsByMerchant(req, res) {
    try {
      const { merchantId } = req.params;
      const { limit = 50, page = 1, status } = req.query;
      const offset = (page - 1) * limit;

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

      // Fetch all customer IDs belonging to this merchant
      const customers = await Customer.findAll({
        where: { merchantId },
        attributes: ["id"],
      });
      const customerIds = customers.map(c => c.id);

      if (customerIds.length === 0) {
        return res.status(200).json({ success: true, total: 0, data: [] });
      }

      const whereClause = { customerId: customerIds };
      if (status) whereClause.status = status;

      const { count, rows } = await Card.findAndCountAll({
        where: whereClause,
        limit: parseInt(limit),
        offset: parseInt(offset),
        order: [["createdAt", "DESC"]],
        attributes: { exclude: ["cvv"] },
        include: [{
          model: Customer,
          as: "customer",
          attributes: ["id", "fullName", "email", "phoneNumber"],
        }],
      });

      return res.status(200).json({
        success: true,
        merchant: { id: merchant.id, businessName: merchant.businessName },
        total: count,
        pages: Math.ceil(count / limit),
        currentPage: parseInt(page),
        data: rows,
      });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * List all virtual cards for a specific customer (admin view, no merchant restriction).
   * GET /admin/customers/:customerId/cards
   */
  static async getCustomerCardsAdmin(req, res) {
    try {
      const { customerId } = req.params;
      const { status } = req.query;

      const customer = await Customer.findByPk(customerId, {
        attributes: ["id", "fullName", "email"],
        include: [{ model: Merchant, attributes: ["businessName"] }],
      });
      if (!customer) {
        return res.status(404).json({ success: false, message: "Customer not found" });
      }

      const whereClause = { customerId };
      if (status) whereClause.status = status;

      const cards = await Card.findAll({
        where: whereClause,
        order: [["createdAt", "DESC"]],
        attributes: { exclude: ["cvv"] },
      });

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

  /**
   * Get full detail of a single card including customer info (Admin view).
   * GET /admin/cards/:cardId
   */
  static async getCardDetailAdmin(req, res) {
    try {
      const { cardId } = req.params;

      const card = await Card.findByPk(cardId, {
        include: [{
          model: Customer,
          as: "customer",
          attributes: ["id", "fullName", "email", "phoneNumber"],
          include: [{ model: Merchant, attributes: ["id", "businessName"] }],
        }],
      });

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

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

  /**
   * Admin Freeze a card (any card, regardless of merchant).
   * POST /admin/cards/:cardId/freeze
   */
  static async adminFreezeCard(req, res) {
    try {
      const { cardId } = req.params;

      const card = await Card.findByPk(cardId);
      if (!card) return res.status(404).json({ success: false, message: "Card not found" });
      if (card.status !== "active") {
        return res.status(422).json({ success: false, message: `Card is already ${card.status}. Cannot freeze.` });
      }

      await PayscribeService.freezeCard(card.card_id);
      await card.update({ status: "frozen" });

      logAudit({
        userId: req.admin.id,
        userType: "admin",
        action: "ADMIN_FREEZE_CARD",
        details: { cardId, card_id: card.card_id, customerId: card.customerId },
        req,
      });

      return res.status(200).json({ success: true, message: "Card frozen successfully by admin" });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * Admin Unfreeze a card.
   * POST /admin/cards/:cardId/unfreeze
   */
  static async adminUnfreezeCard(req, res) {
    try {
      const { cardId } = req.params;

      const card = await Card.findByPk(cardId);
      if (!card) return res.status(404).json({ success: false, message: "Card not found" });
      if (card.status !== "frozen") {
        return res.status(422).json({ success: false, message: `Card is not frozen. Current status: ${card.status}` });
      }

      await PayscribeService.unfreezeCard(card.card_id);
      await card.update({ status: "active" });

      logAudit({
        userId: req.admin.id,
        userType: "admin",
        action: "ADMIN_UNFREEZE_CARD",
        details: { cardId, card_id: card.card_id, customerId: card.customerId },
        req,
      });

      return res.status(200).json({ success: true, message: "Card unfrozen successfully by admin" });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * Admin Terminate a card (refunds remaining balance to customer's USD service account).
   * POST /admin/cards/:cardId/terminate
   */
  static async adminTerminateCard(req, res) {
    try {
      const { cardId } = req.params;

      const card = await Card.findByPk(cardId);
      if (!card) return res.status(404).json({ success: false, message: "Card not found" });
      if (card.status === "terminated") {
        return res.status(422).json({ success: false, message: "Card is already terminated" });
      }

      const remainingBalance = parseFloat(card.balance);

      await PayscribeService.terminateCard(card.card_id);

      // Refund remaining balance back to customer's USD service balance
      if (remainingBalance > 0) {
        const usdService = await db.SystemService.findOne({ where: { code: "usd_virtual_account_service" } });
        if (usdService) {
          await db.CustomerServiceBalance.increment(
            { balance: remainingBalance },
            { where: { customerId: card.customerId, serviceId: usdService.id } }
          );
        }
      }

      await card.update({ status: "terminated", prev_balance: card.balance, balance: 0 });

      logAudit({
        userId: req.admin.id,
        userType: "admin",
        action: "ADMIN_TERMINATE_CARD",
        details: { cardId, card_id: card.card_id, customerId: card.customerId, refundedAmount: remainingBalance },
        req,
      });

      return res.status(200).json({
        success: true,
        message: "Card terminated by admin. Remaining balance refunded to customer's USD account.",
        data: { refunded: remainingBalance, currency: card.currency },
      });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * Admin Update Card Balance — manual credit or debit with transaction heading + description.
   * Used for administrative balance corrections or adjustments.
   *
   * Body:
   *  - operation   {string}  "credit" (add funds) | "debit" (remove funds)
   *  - amount      {number}  Amount to add or remove
   *  - title       {string}  Short heading for this adjustment (e.g. "Balance Correction")
   *  - description {string}  Full explanation of why this adjustment was made
   *
   * PATCH /admin/cards/:cardId/balance
   */
  static async adminUpdateCardBalance(req, res) {
    try {
      const { cardId } = req.params;
      const { operation, amount, title, description } = req.body;

      // --- Validation ---
      if (!operation || !amount || !title || !description) {
        return res.status(400).json({
          success: false,
          message: "operation, amount, title, and description are all required",
        });
      }

      if (!["credit", "debit"].includes(operation)) {
        return res.status(400).json({
          success: false,
          message: "operation must be 'credit' (add funds) or 'debit' (remove funds)",
        });
      }

      const numAmount = parseFloat(amount);
      if (isNaN(numAmount) || numAmount <= 0) {
        return res.status(400).json({ success: false, message: "amount must be a positive number" });
      }

      const card = await Card.findByPk(cardId, {
        include: [{ model: Customer, as: "customer", attributes: ["id", "fullName", "email"] }],
      });
      if (!card) return res.status(404).json({ success: false, message: "Card not found" });
      if (card.status === "terminated") {
        return res.status(422).json({ success: false, message: "Cannot update balance of a terminated card" });
      }

      const previousBalance = parseFloat(card.balance);
      let newBalance;

      if (operation === "credit") {
        newBalance = previousBalance + numAmount;
      } else {
        // debit
        if (numAmount > previousBalance) {
          return res.status(422).json({
            success: false,
            message: `Insufficient card balance. Card has ${previousBalance} ${card.currency}, you are trying to debit ${numAmount}`,
          });
        }
        newBalance = previousBalance - numAmount;
      }

      await card.update({ prev_balance: previousBalance, balance: newBalance });

      logAudit({
        userId: req.admin.id,
        userType: "admin",
        action: `ADMIN_CARD_BALANCE_${operation.toUpperCase()}`,
        details: {
          cardId,
          card_id: card.card_id,
          customerId: card.customerId,
          operation,
          amount: numAmount,
          previousBalance,
          newBalance,
          title,
          description,
        },
        req,
      });

      return res.status(200).json({
        success: true,
        message: `Card balance ${operation === "credit" ? "credited" : "debited"} successfully`,
        data: {
          cardId,
          card_id: card.card_id,
          customer: card.customer,
          currency: card.currency,
          operation,
          amount: numAmount,
          previousBalance,
          newBalance,
          adjustment: {
            title,
            description,
            performedBy: req.admin.id,
            performedAt: new Date(),
          },
        },
      });
    } catch (error) {
      return res.status(500).json({ success: false, message: error.message });
    }
  }
}

export default AdminController;
