// Helper Functions
const mongoose = require('mongoose');
const moment = require('moment');
const Order = require('../models/order.model');
const OrderProduct = require('../models/orderProduct.model');
const Product = require('../models/product.model');
const Account = require('../models/account.model');
const Wallet = require('../models/wallet.model');
const Transaction = require('../models/transactions.model');
const OrderTransaction = require('../models/orderTransactions.model');
const axios = require('axios');

/**
 * Validate order creation input
 */
exports.validateOrderCreation = (userId, products, address, paymentMode) => {
  if (
    !userId ||
    !products ||
    !Array.isArray(products) ||
    products.length === 0 ||
    !address
  ) {
    throw new Error('Invalid input data');
  }

  if (!['full', 'installment'].includes(paymentMode)) {
    throw new Error('Invalid payment mode');
  }
};

/**
 * Validate products for order
 */
exports.validateProductsForOrder = async (
  products,
  userId,
  paymentMode,
  installmentPlanName,
  session,
) => {
  let totalAmount = 0;
  const productDetails = [];
  let selectedInstallmentPlan = null;

  for (const item of products) {
    if (!mongoose.Types.ObjectId.isValid(item.productId)) {
      throw new Error(`Invalid product ID format: ${item.productId}`);
    }

    const product = await Product.findById(item.productId).session(session);
    if (!product) {
      throw new Error(`Product not found: ${item.productId}`);
    }

    if (product.userId.toString() === userId) {
      throw new Error('You cannot patronize yourself');
    }

    if (product.stock < item.quantity) {
      throw new Error(`Insufficient stock for ${product.name}`);
    }

    if (product.swappable) {
      throw new Error(`Product ${product.name} is swappable`);
    }

    if (paymentMode === 'installment') {
      if (!product.installmentAvailable) {
        throw new Error(`Installment not available for ${product.name}`);
      }

      if (!installmentPlanName) {
        throw new Error('Installment plan required');
      }

      const plan = product.installmentPlans.find(
        (p) => p.name === installmentPlanName,
      );
      if (!plan) {
        throw new Error(`Invalid plan for ${product.name}`);
      }

      if (!selectedInstallmentPlan) {
        selectedInstallmentPlan = plan;
      } else if (selectedInstallmentPlan.name !== plan.name) {
        throw new Error('All products must have same installment plan');
      }

      totalAmount +=
        (product.installmentTotalAmount || product.price) * item.quantity;
    } else {
      totalAmount += product.price * item.quantity;
    }

    productDetails.push({
      productId: product._id,
      quantity: item.quantity,
      price:
        paymentMode === 'installment'
          ? product.installmentTotalAmount || product.price
          : product.price,
      name: product.name,
      sellerId: product.userId,
    });
  }

  return { totalAmount, productDetails, selectedInstallmentPlan };
};

/**
 * Process wallet payment for order
 */
exports.processWalletPayment = async (order, amountToPay, userId, session) => {
  const wallet = await Wallet.findOne({ user: userId }).session(session);
  if (!wallet) throw new Error('Wallet not found');

  if (wallet.balance < amountToPay) {
    throw new Error('Insufficient wallet balance');
  }

  const reference = `WALLET-${Date.now()}-${order.paymentMode}`;
  const [transactionRecord] = await Transaction.create(
    [
      {
        amount: amountToPay,
        reference,
        type: 'debit',
        method: 'wallet',
        purpose: 'purchase',
        sender: userId,
        receiver: order.user,
        description: `Payment for order #${order._id}`,
        installmentNumber: order.paymentMode === 'installment' ? 1 : null,
        totalInstallments: order.installmentPlan?.numberOfPayments || null,
        status: 'success',
      },
    ],
    { session },
  );

  wallet.balance -= amountToPay;
  await wallet.save({ session });

  order.amountPaid += amountToPay;
  order.paymentCompleted = order.paymentMode === 'full';
  await order.save({ session });

  await OrderTransaction.create(
    [
      {
        order: order._id,
        transaction: transactionRecord._id,
      },
    ],
    { session },
  );

  return transactionRecord;
};

/**
 * Process Paystack payment for order
 */
exports.processPaystackPayment = async (
  order,
  amountToPay,
  userId,
  session,
) => {
  const user = await Account.findById(userId).session(session);
  if (!user) throw new Error('User not found');

  const reference = `PAYSTACK-${Date.now()}-${order.paymentMode}`;

  const paystackResponse = await axios.post(
    'https://api.paystack.co/transaction/initialize',
    {
      email: user.email,
      amount: Number(amountToPay) * 100,
      reference,
      callback_url: `${process.env.FRONTEND_URL}/order/verify/${order._id}`,
      metadata: {
        orderId: order._id.toString(),
        userId: user._id.toString(),
        paymentMode: order.paymentMode,
        installmentNumber: order.paymentMode === 'installment' ? 1 : null,
        totalInstallments: order.installmentPlan?.numberOfPayments || null,
      },
    },
    {
      headers: {
        Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
      },
    },
  );

  const [transactionRecord] = await Transaction.create(
    [
      {
        amount: amountToPay,
        reference,
        type: 'debit',
        method: 'paystack',
        purpose: 'purchase',
        sender: userId,
        receiver: order.user,
        description: `Payment for order #${order._id}`,
        installmentNumber: order.paymentMode === 'installment' ? 1 : null,
        totalInstallments: order.installmentPlan?.numberOfPayments || null,
        status: 'pending',
        paystackData: paystackResponse.data,
      },
    ],
    { session },
  );

  const wallet = await Wallet.findOne({ user: userId }).session(session);
  if (wallet) {
    await OrderTransaction.create(
      [
        {
          order: order._id,
          transaction: transactionRecord._id,
        },
      ],
      { session },
    );
  }

  order.amountPaid += amountToPay;
  order.paymentCompleted = order.paymentMode === 'full';
  await order.save({ session });

  return {
    paymentUrl: paystackResponse.data.data.authorization_url,
    transactionRecord,
  };
};

/**
 * Process installment payment
 */
exports.processInstallmentPayment = async (
  order,
  installmentNumber,
  paymentMethod,
  session,
) => {
  const installmentAmount =
    order.totalAmount / order.installmentPlan.numberOfPayments;
  const amountRemaining = order.totalAmount - order.amountPaid;
  const currentPayment = Math.min(installmentAmount, amountRemaining);

  const user = await Account.findById(order.user).session(session);
  const wallet = await Wallet.findOne({ user: order.user }).session(session);

  if (paymentMethod === 'wallet') {
    if (!wallet) throw new Error('Wallet not found');
    if (wallet.balance < currentPayment) {
      throw new Error('Insufficient wallet balance for installment');
    }

    const [transactionRecord] = await Transaction.create(
      [
        {
          amount: currentPayment,
          reference: `WALLET-INSTALL-${Date.now()}-${installmentNumber}`,
          type: 'debit',
          method: 'wallet',
          purpose: 'purchase',
          sender: order.user,
          receiver: order.user,
          description: `Installment ${installmentNumber} payment for order #${order._id}`,
          installmentNumber: parseInt(installmentNumber),
          totalInstallments: order.installmentPlan.numberOfPayments,
          status: 'success',
        },
      ],
      { session },
    );

    wallet.balance -= currentPayment;
    await wallet.save({ session });

    order.amountPaid += currentPayment;
    if (Math.abs(order.amountPaid - order.totalAmount) < 0.01) {
      order.paymentCompleted = true;
      order.status = 'paid';
    } else {
      order.status = 'partially paid';
    }
    await order.save({ session });

    return transactionRecord;
  } else if (paymentMethod === 'paystack') {
    const paystackResponse = await axios.post(
      'https://api.paystack.co/transaction/initialize',
      {
        email: user.email,
        amount: currentPayment * 100,
        reference: `PAYSTACK-INSTALL-${Date.now()}-${installmentNumber}`,
        callback_url: `${process.env.FRONTEND_URL}/order/verify-installment/${order._id}/${installmentNumber}`,
        metadata: {
          orderId: order._id.toString(),
          userId: order.user.toString(),
          paymentMode: 'installment',
          installmentNumber: parseInt(installmentNumber),
          totalInstallments: order.installmentPlan.numberOfPayments,
        },
      },
      {
        headers: {
          Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
        },
      },
    );

    return {
      paymentUrl: paystackResponse.data.data.authorization_url,
      installmentNumber: parseInt(installmentNumber),
      totalInstallments: order.installmentPlan.numberOfPayments,
    };
  }

  throw new Error('Invalid payment method');
};

/**
 * Verify Paystack payment
 */
exports.verifyPaystackPayment = async (orderId, reference, session) => {
  if (!mongoose.Types.ObjectId.isValid(orderId)) {
    throw new Error('Invalid order ID format');
  }

  const payment = await axios.get(
    `https://api.paystack.co/transaction/verify/${reference}`,
    {
      headers: {
        Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
      },
    },
  );

  if (payment.data.data.status !== 'success') {
    throw new Error('Payment not successful');
  }

  const amountPaid = payment.data.data.amount / 100;

  const order = await Order.findById(orderId)
    .populate({
      path: 'products.product',
      model: 'Product',
    })
    .session(session);

  if (!order) throw new Error('Order not found');

  const wallet = await Wallet.findOne({ user: order.user }).session(session);

  let transactionRecord = await Transaction.findOne({ reference }).session(
    session,
  );

  if (order.paymentCompleted) {
    return { order, transactionRecord };
  }

  if (transactionRecord) {
    transactionRecord.status = 'success';
    transactionRecord.paystackData = payment.data;
    await transactionRecord.save({ session });
  }

  if (!transactionRecord) {
    [transactionRecord] = await Transaction.create(
      [
        {
          amount: amountPaid,
          reference,
          type: 'debit',
          method: 'paystack',
          purpose: 'purchase',
          sender: order.user,
          receiver: order.user,
          paystackData: payment.data,
          status: 'success',
          description: `Paystack payment for order #${order._id}`,
        },
      ],
      { session },
    );

    await OrderTransaction.create(
      [
        {
          order: order._id,
          transaction: transactionRecord._id,
        },
      ],
      { session },
    );
  }

  if (wallet) {
    await OrderTransaction.create(
      [
        {
          order: order._id,
          transaction: transactionRecord._id,
        },
      ],
      { session },
    );
  }

  order.amountPaid = amountPaid;
  order.paymentCompleted = true;
  order.status = 'paid';
  await order.save({ session });

  // Get order products to update stock
  const orderProducts = await OrderProduct.find({ order: orderId }).session(
    session,
  );

  // Update product stocks
  await Promise.all(
    orderProducts.map(async (item) => {
      const product = await Product.findById(item.product).session(session);
      if (product) {
        product.stock -= item.quantity;
        await product.save({ session });
      }
    }),
  );

  return { order, transactionRecord };
};

/**
 * Verify installment payment
 */
exports.verifyInstallmentPayment = async (
  orderId,
  installmentNumber,
  reference,
  session,
) => {
  if (!mongoose.Types.ObjectId.isValid(orderId)) {
    throw new Error('Invalid order ID format');
  }

  const payment = await axios.get(
    `https://api.paystack.co/transaction/verify/${reference}`,
    {
      headers: {
        Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
      },
    },
  );

  if (payment.data.status !== 'success') {
    throw new Error('Payment not successful');
  }

  const amountPaid = payment.data.amount / 100;

  const order = await Order.findById(orderId)
    .populate({
      path: 'products.product',
      model: 'Product',
    })
    .session(session);

  if (!order) throw new Error('Order not found');
  if (order.paymentCompleted) throw new Error('Order is already fully paid');

  const installmentAmount =
    order.totalAmount / order.installmentPlan.numberOfPayments;
  const amountRemaining = order.totalAmount - order.amountPaid;
  const currentPayment = Math.min(installmentAmount, amountRemaining);

  if (Math.abs(amountPaid - currentPayment) > 0.01) {
    throw new Error('Payment amount does not match installment amount');
  }

  const wallet = await Wallet.findOne({ user: order.user }).session(session);

  const [transactionRecord] = await Transaction.create(
    [
      {
        amount: amountPaid,
        reference,
        type: 'debit',
        method: 'paystack',
        purpose: 'purchase',
        sender: order.user,
        receiver: order.user,
        paystackData: payment.data,
        status: 'success',
        description: `Paystack payment for installment ${installmentNumber} of order #${order._id}`,
        installmentNumber: parseInt(installmentNumber),
        totalInstallments: order.installmentPlan.numberOfPayments,
      },
    ],
    { session },
  );

  if (wallet) {
    await OrderTransaction.create(
      [
        {
          order: order._id,
          transaction: transactionRecord._id,
        },
      ],
      { session },
    );
  }

  order.amountPaid += amountPaid;
  if (Math.abs(order.amountPaid - order.totalAmount) < 0.01) {
    order.paymentCompleted = true;
    order.status = 'paid';
  } else {
    order.status = 'partially paid';
  }
  await order.save({ session });

  return { order, transactionRecord };
};

/**
 * Release payment to seller after order completion
 */
exports.releasePaymentToSeller = async (orderId, session) => {
  if (!mongoose.Types.ObjectId.isValid(orderId)) {
    throw new Error('Invalid order ID format');
  }

  const order = await Order.findById(orderId)
    .populate({
      path: 'products.product',
      model: 'Product',
    })
    .session(session);

  if (!order) throw new Error('Order not found');
  if (!order.paymentCompleted) throw new Error('Order payment not completed');
  if (order.status !== 'completed') throw new Error('Order not yet completed');

  // Check if payment was already released
  const existingRelease = await Transaction.findOne({
    order: orderId,
    purpose: 'seller_payment',
  }).session(session);

  if (existingRelease) {
    throw new Error('Payment already released to seller');
  }

  // Get order products to find sellers
  const orderProducts = await OrderProduct.find({ order: orderId })
    .populate('product')
    .session(session);

  // Group payments by seller
  const sellerPayments = {};

  for (const op of orderProducts) {
    const sellerId = op.product.userId.toString();
    if (!sellerPayments[sellerId]) {
      sellerPayments[sellerId] = 0;
    }
    sellerPayments[sellerId] += op.product.price * op.quantity;
  }

  const releasedTransactions = [];

  // Release payments to each seller
  for (const [sellerId, amount] of Object.entries(sellerPayments)) {
    const sellerWallet = await Wallet.findOne({ user: sellerId }).session(
      session,
    );
    if (!sellerWallet)
      throw new Error(`Seller wallet not found for user ${sellerId}`);

    // Create credit transaction for seller
    const [transactionRecord] = await Transaction.create(
      [
        {
          amount,
          reference: `SELLER-PAYMENT-${Date.now()}-${orderId}-${sellerId}`,
          type: 'credit',
          method: 'wallet',
          purpose: 'seller_payment',
          sender: order.user,
          receiver: sellerId,
          description: `Payment for completed order #${orderId}`,
          status: 'success',
        },
      ],
      { session },
    );

    sellerWallet.balance += amount;
    await sellerWallet.save({ session });

    releasedTransactions.push(transactionRecord);
  }

  return releasedTransactions;
};
