import db from "../models/index.js";
import WebhookService from "../services/webhookService.js";
import TransactionController from "./transactionController.js";
import { Op } from "sequelize";
import DiditService from "../thirdParties/diditService.js";
import { applyDiditKybDecision } from "../services/kybDecisionService.js";

const { MerchantWebhookLog, WebhookLog, Customer, Merchant } = db;

class WebhookController {
  /**
   * GET /merchants/webhooks/outgoing
   * List logs of webhooks sent TO the merchant.
   */
  static async getOutgoingLogs(req, res) {
    try {
      const merchantId = req.merchant.id;
      const { page = 1, limit = 20, status } = req.query;
      const offset = (page - 1) * limit;

      const where = { merchantId };
      if (status) where.status = status;

      const { count, rows } = await MerchantWebhookLog.findAndCountAll({
        where,
        limit: parseInt(limit),
        offset: parseInt(offset),
        order: [["createdAt", "DESC"]],
      });

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

  /**
   * POST /merchants/webhooks/outgoing/:id/resend
   * Manually resend an already logged outgoing webhook.
   */
  static async resendOutgoingWebhook(req, res) {
    try {
      const merchantId = req.merchant.id;
      const { id } = req.params;

      const log = await MerchantWebhookLog.findOne({
        where: { id, merchantId },
      });

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

      // Initial local retry loop (handles status update to delivered/failed)
      const delivered = await WebhookService.executeDelivery(log);

      return res.status(200).json({
        success: delivered,
        message: delivered ? "Webhook resent successfully" : "Resend failed",
        data: log,
      });
    } catch (error) {
      console.error("❌ resendOutgoingWebhook error:", error);
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * GET /merchants/webhooks/incoming
   * List logs of raw webhooks received FROM 9PSB that belong to this merchant.
   */
  static async getIncomingLogs(req, res) {
    try {
      const merchantId = req.merchant.id;
      const { page = 1, limit = 20, status } = req.query;
      const offset = (page - 1) * limit;

      // 1. Get all relevant account numbers for this merchant
      const customers = await Customer.findAll({
        where: { merchantId },
        attributes: ["accountNumber"],
      });
      const merchant = await Merchant.findByPk(merchantId, {
        attributes: ["merchantAccountNumber"]
      });

      const accountNumbers = customers.map((c) => c.accountNumber).filter(Boolean);
      if (merchant?.merchantAccountNumber) {
        accountNumbers.push(merchant.merchantAccountNumber);
      }

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

      // 2. Query WebhookLog by payload content
      // Using JSON path search with fallback field paths
      const where = {
        [Op.or]: [
          db.sequelize.where(db.sequelize.json("payload.customer.account.number"), { [Op.in]: accountNumbers }),
          db.sequelize.where(db.sequelize.json("payload.accountNumber"), { [Op.in]: accountNumbers }),
          db.sequelize.where(db.sequelize.json("payload.destinationAccount"), { [Op.in]: accountNumbers }),
          db.sequelize.where(db.sequelize.json("payload.account_number"), { [Op.in]: accountNumbers })
        ]
      };

      if (status) where.status = status;

      const { count, rows } = await WebhookLog.findAndCountAll({
        where,
        limit: parseInt(limit),
        offset: parseInt(offset),
        order: [["createdAt", "DESC"]],
      });

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

  /**
   * POST /merchants/webhooks/incoming/:id/retry
   * Re-process a failed incoming 9PSB webhook.
   */
  static async retryIncomingWebhook(req, res) {
    try {
      const merchantId = req.merchant.id;
      const { id } = req.params;

      // 1. Fetch the log and verify it belongs to the merchant
      const log = await WebhookLog.findByPk(id);
      if (!log) {
        return res.status(404).json({ success: false, message: "Incoming webhook log not found" });
      }

      const p = log.payload || {};
      const accountNumber =
        p.customer?.account?.number ||
        p.customer?.accountNumber ||
        p.accountNumber ||
        p.destinationAccount ||
        p.account_number ||
        p.destinationAccountNumber ||
        p.virtualAccountNumber;

      const isCustomer = await Customer.findOne({ where: { accountNumber, merchantId } });
      const isMerchant = await Merchant.findOne({ where: { merchantAccountNumber: accountNumber, id: merchantId } });

      if (!isCustomer && !isMerchant) {
        return res.status(403).json({ success: false, message: "Unauthorized: This log does not belong to your account" });
      }

      // 2. Determine if it was already processed
      const sessionId =
        p.transaction?.sessionid ||
        p.transaction?.id ||
        p.sessionid ||
        p.sessionId ||
        p.reference ||
        p.transactionReference ||
        p.order?.reference;

      const existingTx = sessionId ? await db.Transaction.findOne({ where: { transaction_id: sessionId } }) : null;

      if (existingTx) {
        // Case A: Already processed. Just resend the notification.
        console.log(`ℹ️ Webhook ${id} already processed (Tx exists). Triggering notification only.`);

        await WebhookService.notifyMerchant(merchantId, "PAYIN_SUCCESS", {
          transactionId: existingTx.transaction_id,
          customerId: existingTx.customerId,
          amount: existingTx.amount,
          fee: existingTx.fee,
          total_amount: existingTx.total_amount,
          currency: existingTx.currency || "NGN",
          reference: log.payload?.transaction?.reference || sessionId,
          transactionDate: log.payload?.transaction?.date,
        }, existingTx.customerId);

        return res.status(200).json({ success: true, message: "Transaction already processed. Notification resent to your URL." });
      }

      // Case B: Not processed. Carry out the operation (credit/debit) and send webhook.
      console.log(`🔄 Retrying full processing for Webhook ${id}...`);
      const result = await TransactionController.processWebhookPayload(log.payload);

      if (result.success) {
        await log.update({ status: "processed", message: "Manually retried and succeeded" });
        return res.status(200).json({ success: true, message: "Transaction processed successfully and notification triggered." });
      } else {
        return res.status(400).json({ success: false, message: `Processing failed: ${result.message}` });
      }
    } catch (error) {
      console.error("❌ retryIncomingWebhook error:", error);
      return res.status(500).json({ success: false, message: error.message });
    }
  }

  /**
   * POST /webhooks/didit
   * Handle incoming webhooks from Didit.me (KYB / KYC)
   */
  static async handleDiditWebhook(req, res) {
    try {
      const signature = req.headers["x-signature-v2"];
      const rawBody = JSON.stringify(req.body); // In a real Express setup, you'd want raw body for accurate HMAC. For this exercise, JSON.stringify works as a rough approximation.

      // Validate signature
      if (!DiditService.verifyWebhookSignature(signature, rawBody)) {
        console.warn("⚠️ [Didit Webhook] Rejected: invalid signature");
        return res.status(401).json({ success: false, message: "Invalid signature" });
      }

      const { event, data } = req.body;

      if (data?.session_kind !== "business" || event !== "status.updated") {
        console.log(`ℹ️ [Didit Webhook] Ignored event (session_kind=${data?.session_kind}, event=${event})`);
        return res.status(200).json({ success: true });
      }

      // Fetch full decision
      const sessionId = data.session_id;
      const decision = await DiditService.getSessionDecision(sessionId);

      // Find the corresponding KYB application
      const application = await db.KybApplication.findOne({
        where: { diditSessionId: sessionId },
        include: [{ model: db.Merchant, as: "merchant" }]
      });

      if (!application) {
        console.warn(`⚠️ [Didit Webhook] No KYB application found for Didit session ${sessionId}`);
        return res.status(200).json({ success: true });
      }

      await applyDiditKybDecision(application, decision, "webhook", req);

      // Respond within 5s as per Didit docs
      return res.status(200).json({ success: true });
    } catch (error) {
      console.error("❌ handleDiditWebhook error:", error);
      return res.status(500).json({ success: false, message: error.message });
    }
  }
}

export default WebhookController;
