import db from "../models/index.js";
import { v4 as uuidv4 } from "uuid";
import PayscribeService from "../thirdParties/payscribeService.js";
import ServiceGuard from "../utils/serviceGuard.js";
import { adjustCustomerServiceBalance } from "../utils/serviceBalance.js";

const { Customer, Card, PayscribePerson, sequelize } = db;

// The system service code that backs card operations (USD virtual account)
const CARD_FUNDING_SERVICE = "usd_virtual_account_service";

class CardController {
  // ─────────────────────────────────────────────────────────────
  // Register Customer on PayScribe (must be called before card creation)
  // POST /customers/:customerId/cards/register
  // ─────────────────────────────────────────────────────────────
  static async registerPayscribePerson(req, res) {
    const { customerId } = req.params;
    const merchantId = req.merchant.id;

    try {
      // Verify card_service access (requires Stage 2 NIN KYC)
      await ServiceGuard.checkAccess(customerId, "card_service");

      const customer = await Customer.findOne({
        where: { id: customerId, merchantId },
        include: [{ model: PayscribePerson, as: "payscribeProfile" }],
      });

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

      if (customer.payscribeProfile) {
        return res.status(409).json({ success: false, message: "Customer already registered on PayScribe." });
      }

      const { firstName, lastName } = splitFullName(customer.fullName);

      const { dateOfBirth, address, selfieUrl, idDocument } = req.body;
      if (!dateOfBirth || !address) {
        return res.status(400).json({ success: false, message: "dateOfBirth and address are required." });
      }

      const psResponse = await PayscribeService.registerCustomerProfile({
        firstName,
        lastName,
        phone: customer.phoneNumber,
        email: customer.email,
        dob: dateOfBirth,
        address,
        bvn: customer.bvn,
        selfieUrl,
        idDocument,
      });

      const payscribePersonId = psResponse?.data?.customer_id || psResponse?.customer_id;
      if (!payscribePersonId) {
        return res.status(502).json({ success: false, message: "PayScribe did not return a valid customer ID." });
      }

      await PayscribePerson.create({ customerId, payscribePersonId });

      return res.status(201).json({
        success: true,
        message: "Customer successfully registered on PayScribe.",
        data: { payscribePersonId },
      });
    } catch (err) {
      console.error("❌ registerPayscribePerson error:", err.message);
      return res.status(500).json({ success: false, message: err.message });
    }
  }

  // ─────────────────────────────────────────────────────────────
  // Create Virtual Card
  // POST /customers/:customerId/cards
  // ─────────────────────────────────────────────────────────────
  static async createCard(req, res) {
    const { customerId } = req.params;
    const merchantId = req.merchant.id;
    const { amount, brand = "visa", dob, address, selfieUrl, idDocument } = req.body;

    if (!amount || parseFloat(amount) <= 0) {
      return res.status(400).json({ success: false, message: "A valid funding amount is required." });
    }

    const numAmount = parseFloat(amount);

    try {
      await ServiceGuard.checkAccess(customerId, "card_service");

      const customer = await Customer.findOne({
        where: { id: customerId, merchantId },
        include: [{ model: PayscribePerson, as: "payscribeProfile" }],
      });

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

      // Auto-register on PayScribe if not already registered
      if (!customer.payscribeProfile) {
        if (!dob || !address) {
          return res.status(400).json({
            success: false,
            message: "dob and address are required for first-time card requests.",
          });
        }
        
        const { firstName, lastName } = splitFullName(customer.fullName);
        const psResponse = await PayscribeService.registerCustomerProfile({
          firstName,
          lastName,
          phone: customer.phoneNumber,
          email: customer.email,
          dob,
          address,
          bvn: customer.bvn,
          selfieUrl,
          idDocument,
        });

        const payscribePersonId = psResponse?.data?.customer_id || psResponse?.customer_id;
        if (!payscribePersonId) {
          return res.status(502).json({ success: false, message: "PayScribe did not return a valid customer ID." });
        }

        customer.payscribeProfile = await PayscribePerson.create({ customerId, payscribePersonId });
      }

      // Retrieve customer's USD balance (lazy-creates if none exists)
      const balanceRecord = await getOrInitBalance(customerId, CARD_FUNDING_SERVICE);
      const cardFee = 0; // TODO: pull from ServicePricing table when configured

      if (parseFloat(balanceRecord.balance) < numAmount + cardFee) {
        return res.status(422).json({
          success: false,
          message: `Insufficient USD balance. Required: ${numAmount + cardFee}, Available: ${balanceRecord.balance}`,
        });
      }

      const reference = `CARD-${uuidv4().slice(0, 12).toUpperCase()}`;

      const dbTxn = await sequelize.transaction();
      try {
        // Call PayScribe to create the card
        const psResponse = await PayscribeService.createVirtualCard({
          customer_id: customer.payscribeProfile.payscribePersonId,
          amount: numAmount,
          brand,
          reference,
        });

        const cardData = psResponse?.data || psResponse;

        // Deduct from customer's USD virtual account balance
        await adjustCustomerServiceBalance(customerId, CARD_FUNDING_SERVICE, -(numAmount + cardFee), dbTxn);

        // Save the card record
        const card = await Card.create({
          customerId,
          card_id: cardData.card_id || cardData.id,
          brand: cardData.brand || brand,
          name: cardData.name || customer.fullName,
          card_number: cardData.card_number,
          cvv: cardData.cvv,
          expiry: cardData.expiry,
          balance: numAmount,
          prev_balance: 0,
          currency: "USD",
          status: "active",
        }, { transaction: dbTxn });

        await dbTxn.commit();

        return res.status(201).json({
          success: true,
          message: "Virtual card created successfully.",
          data: card,
        });
      } catch (innerErr) {
        await dbTxn.rollback();
        console.error("❌ createCard DB error:", innerErr.message);
        return res.status(500).json({ success: false, message: innerErr.message });
      }
    } catch (err) {
      console.error("❌ createCard error:", err.message);
      return res.status(500).json({ success: false, message: err.message });
    }
  }

  // ─────────────────────────────────────────────────────────────
  // Top-up a Card
  // POST /customers/:customerId/cards/:cardId/topup
  // ─────────────────────────────────────────────────────────────
  static async topUpCard(req, res) {
    const { customerId, cardId } = req.params;
    const { amount } = req.body;

    if (!amount || parseFloat(amount) <= 0) {
      return res.status(400).json({ success: false, message: "A valid top-up amount is required." });
    }

    const numAmount = parseFloat(amount);

    try {
      const card = await Card.findOne({ where: { id: cardId, customerId } });
      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 ${card.status}. Cannot top-up.` });
      }

      const balanceRecord = await getOrInitBalance(customerId, CARD_FUNDING_SERVICE);
      if (parseFloat(balanceRecord.balance) < numAmount) {
        return res.status(422).json({
          success: false,
          message: `Insufficient USD balance. Required: ${numAmount}, Available: ${balanceRecord.balance}`,
        });
      }

      const reference = `TOPUP-${uuidv4().slice(0, 12).toUpperCase()}`;

      const dbTxn = await sequelize.transaction();
      try {
        await PayscribeService.topUpCard(card.card_id, { amount: numAmount, reference });

        // Debit USD service balance
        await adjustCustomerServiceBalance(customerId, CARD_FUNDING_SERVICE, -numAmount, dbTxn);

        // Credit card balance
        await card.update(
          { prev_balance: card.balance, balance: parseFloat(card.balance) + numAmount },
          { transaction: dbTxn },
        );

        await dbTxn.commit();

        return res.status(200).json({
          success: true,
          message: "Card topped up successfully.",
          data: { card_id: card.card_id, balance: card.balance },
        });
      } catch (innerErr) {
        await dbTxn.rollback();
        console.error("❌ topUpCard DB error:", innerErr.message);
        return res.status(500).json({ success: false, message: innerErr.message });
      }
    } catch (err) {
      console.error("❌ topUpCard error:", err.message);
      return res.status(500).json({ success: false, message: err.message });
    }
  }

  // ─────────────────────────────────────────────────────────────
  // Withdraw from Card
  // POST /customers/:customerId/cards/:cardId/withdraw
  // ─────────────────────────────────────────────────────────────
  static async withdrawFromCard(req, res) {
    const { customerId, cardId } = req.params;
    const { amount } = req.body;

    if (!amount || parseFloat(amount) <= 0) {
      return res.status(400).json({ success: false, message: "A valid withdrawal amount is required." });
    }

    const numAmount = parseFloat(amount);

    try {
      const card = await Card.findOne({ where: { id: cardId, customerId } });
      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 ${card.status}. Cannot withdraw.` });
      }
      if (parseFloat(card.balance) < numAmount) {
        return res.status(422).json({
          success: false,
          message: `Insufficient card balance. Card has ${card.balance} ${card.currency}.`,
        });
      }

      const dbTxn = await sequelize.transaction();
      try {
        await PayscribeService.withdrawFromCard(card.card_id, { amount: numAmount });

        // Debit card balance
        await card.update(
          { prev_balance: card.balance, balance: parseFloat(card.balance) - numAmount },
          { transaction: dbTxn },
        );

        // Credit customer's USD service balance
        await adjustCustomerServiceBalance(customerId, CARD_FUNDING_SERVICE, +numAmount, dbTxn);

        await dbTxn.commit();

        return res.status(200).json({
          success: true,
          message: "Withdrawal from card successful.",
          data: { card_id: card.card_id, card_balance: card.balance },
        });
      } catch (innerErr) {
        await dbTxn.rollback();
        console.error("❌ withdrawFromCard DB error:", innerErr.message);
        return res.status(500).json({ success: false, message: innerErr.message });
      }
    } catch (err) {
      console.error("❌ withdrawFromCard error:", err.message);
      return res.status(500).json({ success: false, message: err.message });
    }
  }

  // ─────────────────────────────────────────────────────────────
  // Freeze Card
  // POST /customers/:customerId/cards/:cardId/freeze
  // ─────────────────────────────────────────────────────────────
  static async freezeCard(req, res) {
    const { customerId, cardId } = req.params;

    try {
      const card = await Card.findOne({ where: { id: cardId, customerId } });
      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}.` });
      }

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

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

  // ─────────────────────────────────────────────────────────────
  // Unfreeze Card
  // POST /customers/:customerId/cards/:cardId/unfreeze
  // ─────────────────────────────────────────────────────────────
  static async unfreezeCard(req, res) {
    const { customerId, cardId } = req.params;

    try {
      const card = await Card.findOne({ where: { id: cardId, customerId } });
      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" });

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

  // ─────────────────────────────────────────────────────────────
  // Terminate Card
  // POST /customers/:customerId/cards/:cardId/terminate
  // ─────────────────────────────────────────────────────────────
  static async terminateCard(req, res) {
    const { customerId, cardId } = req.params;

    try {
      const card = await Card.findOne({ where: { id: cardId, customerId } });
      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);

      const dbTxn = await sequelize.transaction();
      try {
        await PayscribeService.terminateCard(card.card_id);

        // Refund remaining card balance back to the customer's USD service balance
        if (remainingBalance > 0) {
          await adjustCustomerServiceBalance(customerId, CARD_FUNDING_SERVICE, +remainingBalance, dbTxn);
        }

        // Update card state
        await card.update(
          { status: "terminated", prev_balance: card.balance, balance: 0 },
          { transaction: dbTxn },
        );

        await dbTxn.commit();

        return res.status(200).json({
          success: true,
          message: "Card terminated. Remaining balance refunded to your USD account.",
          data: { refunded: remainingBalance, currency: card.currency },
        });
      } catch (innerErr) {
        await dbTxn.rollback();
        console.error("❌ terminateCard DB error:", innerErr.message);
        return res.status(500).json({ success: false, message: innerErr.message });
      }
    } catch (err) {
      console.error("❌ terminateCard error:", err.message);
      return res.status(500).json({ success: false, message: err.message });
    }
  }

  // ─────────────────────────────────────────────────────────────
  // Get Customer Cards
  // GET /customers/:customerId/cards
  // ─────────────────────────────────────────────────────────────
  static async getCustomerCards(req, res) {
    const { customerId } = req.params;
    const merchantId = req.merchant.id;

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

      const cards = await Card.findAll({
        where: { customerId },
        order: [["createdAt", "DESC"]],
        attributes: { exclude: ["cvv"] }, // Never expose CVV in list
      });

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

  // ─────────────────────────────────────────────────────────────
  // Payscribe Webhook
  // POST /webhooks/payscribe
  // ─────────────────────────────────────────────────────────────
  static async handlePayscribeWebhook(req, res) {
    try {
      const event = req.body;
      const cardId = event?.data?.card_id;

      if (!cardId) return res.status(200).json({ received: true });

      const card = await Card.findOne({ where: { card_id: cardId } });
      if (!card) return res.status(200).json({ received: true });

      const eventType = event.event;
      const txAmount = parseFloat(event?.data?.amount || 0);

      if (eventType === "card.transaction") {
        // Debit the card balance on successful spend
        await card.update({
          prev_balance: card.balance,
          balance: Math.max(0, parseFloat(card.balance) - txAmount),
        });
      } else if (eventType === "card.refund") {
        // Credit back on refund
        await card.update({
          prev_balance: card.balance,
          balance: parseFloat(card.balance) + txAmount,
        });
      } else if (eventType === "card.decline") {
        // Log the decline – optionally charge a decline fee (configurable)
        console.warn(`⚠️ Card decline event received for card_id: ${cardId}`);
      }

      return res.status(200).json({ received: true });
    } catch (err) {
      console.error("❌ handlePayscribeWebhook error:", err.message);
      return res.status(500).json({ success: false, message: err.message });
    }
  }
}

// ─────────────────────────────────────────────────────────────
// Helpers
// ─────────────────────────────────────────────────────────────

function splitFullName(fullName = "") {
  const parts = fullName.trim().split(" ");
  return { firstName: parts[0] || "", lastName: parts.slice(1).join(" ") || parts[0] || "" };
}

async function getOrInitBalance(customerId, serviceCode) {
  const service = await db.SystemService.findOne({ where: { code: serviceCode } });
  if (!service) throw new Error(`Service '${serviceCode}' not found.`);

  const [record] = await db.CustomerServiceBalance.findOrCreate({
    where: { customerId, serviceId: service.id },
    defaults: { balance: 0.00, prev_balance: 0.00 },
  });
  return record;
}

export default CardController;
