const crypto = require('crypto');
const { Bookings, Transactions, Listings, BookingAdditionalPayments } = require('../models');
const { parseAmount } = require('../services/payMongo');
const { saveMessage, findOrCreateConversation } = require('../services');
const logger = require('../util/logger');
const { formatJsonLog } = require('../util/formatter');
const { calculateIncomes } = require('../helpers/booking-helper');

function verifyPaymongoWebhook(req, secret, mode = 'test') {
  try {
    const signatureHeader = req.headers['paymongo-signature'];
    if (!signatureHeader) return false;

    const parts = {};
    signatureHeader.split(',').forEach((part) => {
      const [key, ...valueParts] = part.trim().split('=');
      parts[key] = valueParts.join('=');
    });

    const timestamp = parts.t;
    const signature = mode === 'live' ? parts.li : parts.te;

    if (!timestamp || !signature) {
      logger.error('Missing timestamp or signature in header');
      return false;
    }

    const rawBody = req.body;
    let bodyString;

    if (Buffer.isBuffer(rawBody)) {
      bodyString = rawBody.toString('utf8');
    } else if (typeof rawBody === 'string') {
      bodyString = rawBody;
    } else {
      bodyString = JSON.stringify(rawBody);
    }

    const sigString = `${timestamp}.${bodyString}`;

    const hmac = crypto.createHmac('sha256', secret).update(sigString, 'utf8').digest('hex');

    const hmacAlt = crypto.createHmac('sha256', secret).update(sigString).digest('hex');

    return hmac === signature || hmacAlt === signature;
  } catch (error) {
    logger.error(
      'Error verifying PayMongo webhook: ' +
        formatJsonLog({
          error: error.message,
          stack: error.stack,
        }),
    );
    return false;
  }
}

exports.handleWebhook = async (req, res) => {
  try {
    const webhookSecret = process.env.PAYMONGO_WEBHOOK_SECRET;

    const isValid = verifyPaymongoWebhook(req, webhookSecret, process.env.PAYMONGO_MODE || 'test');
    if (!isValid) {
      logger.error('Invalid PayMongo webhook signature');
      return res.status(200).json({ message: 'SUCCESS', error: 'Invalid signature' });
    }

    const event = Buffer.isBuffer(req.body) ? JSON.parse(req.body.toString()) : req.body;

    const eventType = event.data.attributes.type;
    let paymentIntentId;

    switch (eventType) {
      case 'payment.paid':
        paymentIntentId = event.data.attributes.data.attributes.payment_intent_id;
        await handlePaymentPaid(paymentIntentId, event);
        break;

      case 'payment.failed':
        paymentIntentId = event.data.attributes.data.attributes.payment_intent_id;
        await handlePaymentFailed(paymentIntentId, event);
        break;

      case 'payment.refunded':
        paymentIntentId = event.data.attributes.data.attributes.payment_intent_id;
        await handleRefund(paymentIntentId, event);
        break;

      default:
        logger.error('Unhandled event type:', eventType, JSON.stringify(event, null, 2));
        break;
    }
  } catch (error) {
    console.error('Webhook processing error:', error);
  }
  res.status(200).json({ message: 'SUCCESS' });
};

const handlePaymentPaid = async (paymentIntentId, sourceData) => {
  try {
    let booking = await Bookings.findOne({
      where: {
        payment_intent_id: paymentIntentId,
      },
      include: [
        {
          model: Listings,
          as: 'listing',
          attributes: ['approval', 'host_id'],
        },
      ],
    });

    const paymongo_payment_id = sourceData?.data?.attributes?.data?.id || null;
    const paymongo_fee = (sourceData?.data?.attributes?.data?.attributes?.fee || 0) / 100;
    const payment_method =
      sourceData?.data?.attributes?.data?.attributes?.source?.type || 'unknown';

    if (!booking) {
      const additionalPayment = await BookingAdditionalPayments.findOne({
        where: { payment_intent_id: paymentIntentId },
      });

      if (additionalPayment) {
        booking = await Bookings.findOne({
          where: { id: additionalPayment.booking_id },
        });

        // Prevent duplicate processing - check if already paid
        if (additionalPayment.status !== 'Paid') {
          const paymentAmount = (sourceData?.data?.attributes?.data?.attributes?.amount || 0) / 100;

          await additionalPayment.update({
            status: 'Paid',
            paymongo_payment_id,
            paymongo_fee,
            payment_method,
            amount_paid: paymentAmount,
          });

          if (additionalPayment.description === 'Host Modification Payment') {
            await booking.update({
              payment_status: 'Fully Paid',
              host_modify_payment_status: 'Paid',
            });

            await Transactions.update(
              {
                status: 'Paid',
                paymongo_fee: paymongo_fee,
              },
              {
                where: {
                  booking_id: booking.id,
                  action: 'Pay Host Modified Booking',
                  payment_intent_id: paymentIntentId,
                },
              },
            );
          } else {
            await booking.update({
              payment_status: 'Fully Paid',
              customer_modify_payment_status: 'Paid',
            });

            await Transactions.update(
              {
                status: 'Paid',
                paymongo_fee: paymongo_fee,
              },
              {
                where: {
                  booking_id: booking.id,
                  type: 'Payment',
                  action: 'Booking Modify',
                  payment_intent_id: paymentIntentId,
                },
              },
            );
          }
        } else {
          logger.info(
            `Payment already processed for intent ${paymentIntentId}, skipping duplicate webhook`,
          );
        }
      }
    } else {
      // Prevent duplicate processing - check if already paid
      if (booking.payment_status !== 'Fully Paid') {
        const paymentAmount = booking.total_amount;
        const net_total = paymentAmount - paymongo_fee;
        const incomes = calculateIncomes(
          net_total,
          booking.guest_fee,
          paymentAmount,
          booking.discount_amount || 0,
          0,
        );

        await booking.update({
          payment_status: 'Fully Paid',
          status: booking.listing?.approval === 'Auto' ? 'Upcoming' : 'For Approval',
          amount_paid: paymentAmount,
          balance: 0,
          paymongo_payment_id: sourceData?.data?.attributes?.data?.id || null,
          paymongo_fee: (sourceData?.data?.attributes?.data?.attributes?.fee || 0) / 100,
          payment_method: sourceData?.data?.attributes?.data?.attributes?.source?.type || 'unknown',
          host_fee: incomes.host_fee,
          guest_fee: incomes.guest_fee,
          aircamp_income: incomes.aircamp_income,
          total_host_income: incomes.total_host_income,
        });

        await Transactions.update(
          {
            status: 'Paid',
          },
          {
            where: { booking_id: booking.id, type: 'Payment' },
          },
        );

        if (booking.listing.approval === 'Auto') {
          const convoId = await findOrCreateConversation(booking.host_id, booking.customer_id);
          await saveMessage(
            convoId,
            booking.host_id,
            `Your booking with reference number ${booking.booking_no} has been Accepted.`,
            null,
            booking.id,
          );
        }
      } else {
        logger.info(
          `Booking ${booking.booking_no} already marked as paid for intent ${paymentIntentId}, skipping duplicate webhook`,
        );
      }
    }
  } catch (error) {
    console.error('Error handling payment paid:', error);
    logger.error(`Error handling payment.paid webhook for intent ${paymentIntentId}:`, error);
  }
  return;
};

const handlePaymentFailed = async (paymentIntentId, sourceData) => {
  try {
    let booking = await Bookings.findOne({
      where: {
        payment_intent_id: paymentIntentId,
      },
    });

    if (!booking) {
      const additionalPayment = await BookingAdditionalPayments.findOne({
        where: { payment_intent_id: paymentIntentId },
      });

      if (additionalPayment) {
        booking = await Bookings.findOne({
          where: { id: additionalPayment.booking_id },
        });

        // Prevent duplicate processing - check if not already failed
        if (additionalPayment.status !== 'Failed') {
          await additionalPayment.update({
            status: 'Failed',
            paymongo_payment_id: sourceData?.data?.attributes?.data?.id || null,
          });

          // Check if this is a Host Modification Payment failure
          if (additionalPayment.description === 'Host Modification Payment') {
            await booking.update({
              host_modify_payment_status: 'Failed',
            });

            await Transactions.update(
              {
                status: 'Failed',
              },
              {
                where: {
                  booking_id: booking.id,
                  action: 'Pay Host Modified Booking',
                  payment_intent_id: paymentIntentId,
                },
              },
            );
          } else {
            // Handle regular customer modification payment failures
            await booking.update({
              customer_modify_payment_status: 'Failed',
            });

            await Transactions.update(
              {
                status: 'Failed',
              },
              {
                where: {
                  booking_id: booking.id,
                  type: 'Payment',
                  action: 'Booking Modify',
                  payment_intent_id: paymentIntentId,
                },
              },
            );
          }
        } else {
          logger.info(
            `Payment already marked as failed for intent ${paymentIntentId}, skipping duplicate webhook`,
          );
        }
      }
    } else {
      // Prevent duplicate processing - check if not already failed
      if (booking.payment_status !== 'Failed') {
        booking.payment_status = 'Failed';
        booking.paymongo_payment_id = sourceData?.data?.attributes?.data?.id || null;

        const transaction = await Transactions.findOne({
          where: { booking_id: booking.id, type: 'Payment' },
        });

        if (transaction) {
          transaction.status = 'Failed';
          await transaction.save();
        }

        await booking.save();
      } else {
        logger.info(
          `Booking ${booking.booking_no} already marked as failed for intent ${paymentIntentId}, skipping duplicate webhook`,
        );
      }
    }
  } catch (error) {
    console.error('Error handling payment failed:', error);
    logger.error(`Error handling payment.failed webhook for intent ${paymentIntentId}:`, error);
  }
  return;
};

const handleRefund = async (paymentIntentId, sourceData) => {
  const booking = await Bookings.findOne({
    where: {
      payment_intent_id: paymentIntentId,
    },
    include: [
      {
        model: Listings,
        as: 'listing',
        attributes: ['approval', 'host_id'],
      },
    ],
  });

  const refundedAmount =
    parseAmount(sourceData?.data?.attributes?.data?.attributes?.refunds?.[0]?.attributes?.amount) ||
    booking.refund_amount ||
    0;

  if (!booking) {
    await BookingAdditionalPayments.update(
      {
        is_refunded: true,
        refund_amount: refundedAmount,
        refunded_at: new Date(),
      },
      { where: { payment_intent_id: paymentIntentId } },
    );

    await Transactions.update(
      {
        status: 'Refunded',
      },
      {
        where: {
          booking_additional_payment_id: booking.id,
        },
      },
    );
  } else {
    const paymongo_fee = booking.paymongo_fee || 0;
    const totalAmount = booking.total_amount || 0;
    const net_total = totalAmount - paymongo_fee - refundedAmount;

    const incomes = calculateIncomes(
      net_total < 0 ? 0 : net_total,
      booking.guest_fee,
      booking.amount_paid || 0,
      booking.discount_amount || 0,
      0,
    );

    await booking.update({
      is_refunded: true,
      refund_amount: refundedAmount,
      refunded_at: new Date(),
      host_fee: incomes.host_fee,
      guest_fee: incomes.guest_fee,
      aircamp_income: incomes.aircamp_income,
      total_host_income: incomes.total_host_income,
    });
  }

  return;
};
