const { refundPayment } = require('../../../services/payMongo');
const { calculateInitialAmounts, calculateIncomes } = require('../../../helpers/booking-helper');
const { checkManagerAccess } = require('../../../helpers/managers');
const { Bookings, Listings, Transactions, AuditTrails, sequelize } = require('../../../models');
const {
  sendEmail,
  cancelledBookingEmail,
  requestModificationEmail,
  modifiedBookingEmail,
} = require('../../../services/emailService');
const {} = require('../../../templates/email');
const { errorHandler } = require('../../../util');
const { handleDiscount } = require('../../customers/v1.1/bookings');

exports.cancelBooking = async (req, res, next) => {
  let transaction;
  try {
    transaction = await sequelize.transaction();
    const { bookingNumber } = req.params;
    const { userId } = req;
    const { reason } = req.body;

    const booking = await Bookings.findOne({
      where: { booking_no: bookingNumber },
    });

    if (!booking) {
      return res.status(404).json({ success: false, message: 'Booking not found' });
    }

    let hostId = req.userId;
    if (req.query.hostId) {
      hostId = (await checkManagerAccess(req, 'Booking')).hostId;
    }
    if (booking.host_id !== hostId) {
      return res
        .status(403)
        .json({ success: false, message: 'You are not authorized to cancel this booking' });
    }

    if (['Cancelled', 'Done'].includes(booking.status)) {
      return res
        .status(400)
        .json({ success: false, message: 'Booking has already been ' + booking.status });
    }

    let refundPercentage = 0;
    let daysBeforeCheckIn;

    if (!booking.is_rescheduled) {
      const checkInDate = new Date(booking.check_in);
      const today = new Date();

      daysBeforeCheckIn = Math.ceil(
        (new Date(checkInDate).getTime() - new Date(today).getTime()) / (1000 * 60 * 60 * 24),
      );

      if (daysBeforeCheckIn >= 15) {
        refundPercentage = 100;
      } else if (daysBeforeCheckIn >= 8) {
        refundPercentage = 80;
      } else if (daysBeforeCheckIn >= 4) {
        refundPercentage = 50;
      } else {
        refundPercentage = 0;
      }
    }

    let updatesDataRaw = booking?.updates_data ?? '{}';
    let parsedUpdatesData;

    try {
      parsedUpdatesData = JSON.parse(updatesDataRaw);

      if (typeof parsedUpdatesData === 'string') {
        parsedUpdatesData = JSON.parse(parsedUpdatesData);
      }
    } catch (e) {
      parsedUpdatesData = {};
    }

    booking.updates_data = JSON.stringify({
      ...parsedUpdatesData,
      cancel: {
        timestamp: new Date(),
        cancelled_by: 'Host',
        refund_percentage: refundPercentage,
        guest_fee: booking.guest_fee,
        host_fee: booking.host_fee,
        host_income: booking.total_host_income,
        aircamp_income: booking.aircamp_income,
      },
    });

    const guest_fee = booking.guest_fee - booking.guest_fee * (refundPercentage / 100);
    const host_fee = booking.host_fee - booking.host_fee * (refundPercentage / 100);
    const host_income =
      booking.total_host_income - booking.total_host_income * (refundPercentage / 100);
    const aircamp_income =
      booking.aircamp_income - booking.aircamp_income * (refundPercentage / 100);

    booking.guest_fee = guest_fee;
    booking.host_fee = host_fee;
    booking.total_host_income = host_income;
    booking.aircamp_income = aircamp_income;

    booking.status = 'Cancelled';
    booking.cancellation_reason = reason;

    const total_refund = Number(booking.total_amount) * (refundPercentage / 100);
    booking.refund_amount = Number(total_refund) - Number(booking.paymongo_fee || 0);
    booking.refund_percentage = refundPercentage;
    booking.is_refunded = false;

    let refunded = false;
    if (!booking.is_rescheduled && refundPercentage > 0) {
      refunded = await refundPayment(
        booking.checkout_session_id,
        total_refund,
        booking.paymongo_fee,
        'Cancelled by Host: ' + reason,
      );

      if (refunded) {
        booking.is_refunded = true;
        booking.refunded_at = new Date();
      }

      await Transactions.create(
        {
          host_id: userId,
          customer_id: booking.customer_id,
          type: 'Refund',
          action: 'Booking Cancellation',
          amount: total_refund,
          status: 'Pending',
          method: null,
        },
        { transaction },
      );
    }

    await booking.save({ transaction });
    await AuditTrails.create(
      {
        host_id: req.query.hostId ?? req.userId,
        booking_id: booking.id,
        user_id: req.query.hostId ? req.userId : null,
        role: req.query.hostId ? 'Manager' : 'Host',
        type: 'Booking',
        description: `Booking with reference number ${booking.booking_no} has been cancelled`,
      },
      {
        transaction,
      },
    );

    await transaction.commit();

    let message;
    if (booking.is_rescheduled) {
      message =
        'Booking cancelled successfully. No refund will be given as booking has been rescheduled.';
    } else {
      message = `Booking cancelled successfully. Refund: ${refundPercentage}%. `;
      if (refundPercentage > 0) {
        message += refunded
          ? 'Refund has been processed.'
          : 'Refund is pending or could not be processed automatically.';
      }
    }

    res.status(200).json({
      success: true,
      message,
      data: {
        daysBeforeCheckIn,
        refundPercentage,
        checkIn: booking.check_in,
        totalRefund: total_refund,
        isRefunded: booking.is_refunded,
      },
    });
  } catch (error) {
    next(error);
  }
};

exports.sendCancelEmail = async (req, res, next) => {
  try {
    const { bookingNumber } = req.params;

    const booking = await Bookings.findOne({
      where: { booking_no: bookingNumber },
    });

    if (!booking) {
      return res.status(404).json({
        success: false,
        message: 'Booking not found',
      });
    }

    if (booking.status != 'Cancelled') {
      return res.status(400).json({
        success: false,
        message: 'Booking is not cancelled',
      });
    }

    const listing = await Listings.findByPk(booking.listing_id);
    const user_name = booking.fullname;
    const data = {
      user_name,
      listing_id: listing.id,
    };

    await sendEmail(cancelledBookingEmail(booking.email, data));

    res.status(200).json({
      success: true,
      message: 'Email sent',
    });
  } catch (error) {
    next(error);
  }
};

exports.requestBookingModification = async (req, res, next) => {
  try {
    const { bookingNo, sendEmailOnResponse } = req.body;

    const booking = await Bookings.findOne({
      where: { booking_no: bookingNo },
    });

    if (!booking) {
      errorHandler('Booking not found', 404);
    }

    let hostId = req.userId;
    if (req.query.hostId) {
      hostId = (await checkManagerAccess(req, 'Booking')).hostId;
    }

    if (booking.host_id !== hostId) {
      errorHandler('You are not authorized to request modification for this booking', 403);
    }

    if (booking.modify_request === 'Pending') {
      errorHandler('Modification request has already been sent', 400);
    }

    await sendEmail(
      requestModificationEmail(booking.email, {
        booking_no: booking.booking_no,
        user_name: booking.fullname,
      }),
    );

    booking.modify_request = 'Pending';
    booking.email_on_response = sendEmailOnResponse;

    await booking.save();

    await AuditTrails.create({
      host_id: req.query.hostId ?? req.userId,
      booking_id: booking.id,
      user_id: req.query.hostId ? req.userId : null,
      role: req.query.hostId ? 'Manager' : 'Host',
      type: 'Booking',
      description: `Booking with reference number ${booking.booking_no} has been requested to be modified`,
    });

    return res.status(200).json({
      success: true,
      message: 'Modification request sent successfully',
    });
  } catch (error) {
    next(error);
  }
};

exports.modifyBooking = async (req, res, next) => {
  try {
    const { newPax, bookingNo } = req.body;

    const booking = await Bookings.findOne({
      where: { booking_no: bookingNo },
    });

    if (!booking) {
      errorHandler('Booking not found', 404);
    }

    let hostId = req.userId;
    if (req.query.hostId) {
      hostId = (await checkManagerAccess(req, 'Booking')).hostId;
    }

    if (booking.host_id !== hostId) {
      errorHandler('You are not authorized to request modification for this booking', 403);
    }

    if (booking.status === 'Cancelled' || booking.status === 'Done') {
      errorHandler('Booking has been ' + booking.status, 400);
    }

    if (!booking.modify_request) {
      errorHandler('Modification request has not been sent', 400);
    }

    if (booking.modify_request !== 'Accepted') {
      errorHandler('Modification request status is ' + booking.modify_request, 400);
    }

    if (booking.pax === newPax) {
      errorHandler('Pax is the same as the current booking', 400);
    }

    const listing = await Listings.findOne({
      where: { id: booking.listing_id },
    });

    if (listing.max_capacity < newPax) {
      errorHandler('Pax exceeds maximum capacity', 400);
    }

    const oldPax = booking.pax;
    booking.pax = newPax;

    const paxDifference = newPax - oldPax;

    const { initial_guest_fee, sub_total } = calculateInitialAmounts(
      listing.price,
      booking.check_in,
      booking.check_out,
    );

    let modifyData = {
      old_total_amount: Number(booking.total_amount),
      current_total_amount: booking.total_amount,
      current_guest_fee: booking.guest_fee,
      current_host_fee: booking.host_fee,
      current_total_host_income: booking.total_host_income,
      current_aircamp_income: booking.aircamp_income,
      current_pax: booking.pax,
    };

    let discount = 0,
      newDiscountData,
      invalidDiscount = false;
    if (booking.discount_code) {
      try {
        const handledDiscount = await handleDiscount(
          booking.discount_code,
          listing,
          sub_total,
          booking.pax,
          booking.customer_id,
        );
        discount = handledDiscount.discount;
        newDiscountData = handledDiscount.discountData;
      } catch (error) {
        invalidDiscount = true;
        newDiscountData = {
          discount_code: null,
          discount_type: null,
          discount_percentage: null,
          discount_amount: null,
          discount_num_of_pax: null,
          discount_amount_to_reach: null,
        };
      }

      modifyData.old_discount_data = {
        discount_code: booking.discount_code,
        discount_type: booking.discount_type,
        discount_percentage: booking.discount_percentage,
        discount_amount: booking.discount_amount,
        discount_num_of_pax: booking.discount_num_of_pax,
        discount_amount_to_reach: booking.discount_amount_to_reach,
      };
    }

    const { grand_total, host_fee, guest_fee, aircamp_income, total_host_income } =
      calculateIncomes(sub_total, initial_guest_fee, 0, discount);

    modifyData.old_guest_fee = booking.guest_fee;
    modifyData.old_host_fee = booking.host_fee;
    modifyData.old_total_host_income = booking.total_host_income;
    modifyData.old_aircamp_income = booking.aircamp_income;
    modifyData.original_total_amount = Number(booking.total_amount);
    modifyData.total_amount_difference = Number(grand_total) - Number(booking.total_amount);

    const additionalPayment = Number(grand_total) - Number(booking.total_amount);

    let total_accumulated_payment = Number(booking.amount_paid);

    let updatesDataRaw = booking?.updates_data ?? '{}';
    let updatesData;

    try {
      updatesData = JSON.parse(updatesDataRaw);
      if (typeof updatesData === 'string') {
        updatesData = JSON.parse(updatesData);
      }
    } catch (e) {
      updatesData = {};
    }

    if (!updatesData?.modify) {
      updatesData.modify = {
        count: 0,
        history: [],
      };
    }

    updatesData.modify.count = updatesData?.modify?.count ? updatesData.modify.count + 1 : 1;
    modifyData = {
      ...modifyData,
      modified_by: 'Host',
      additional_payment: additionalPayment,
      new_total_amount: grand_total,
      new_pax: newPax,
      old_pax: oldPax,
      timestamp: new Date(),
    };

    if (updatesData?.modify?.history) {
      updatesData.modify.history.push(modifyData);
    } else {
      updatesData.modify.history = [modifyData];
    }

    booking.total_amount = grand_total;
    booking.updates_data = JSON.stringify(updatesData);
    booking.guest_fee = guest_fee;
    booking.host_fee = host_fee;
    booking.aircamp_income = aircamp_income;
    booking.total_host_income = total_host_income;
    booking.amount_paid = total_accumulated_payment;
    booking.subtotal = sub_total;
    booking.host_modification_fee = additionalPayment > 0 ? additionalPayment : 0;
    booking.payment_status = additionalPayment > 0 ? 'Unpaid' : booking.payment_status;
    booking.modify_request = null;
    booking.email_on_response = null;

    for (const key in newDiscountData) {
      booking[key] = newDiscountData[key];
    }

    await booking.save();

    await AuditTrails.create({
      host_id: req.query.hostId ?? req.userId,
      booking_id: booking.id,
      user_id: req.query.hostId ? req.userId : null,
      role: req.query.hostId ? 'Manager' : 'Host',
      type: 'Booking',
      description: `Booking with reference number ${booking.booking_no} has been modified`,
    });

    const difference = modifyData.total_amount_difference;
    if (difference < 0) {
      await Transactions.create({
        host_id: booking.host_id,
        booking_id: booking.id,
        customer_id: booking.customer_id,
        type: 'Refund',
        action: 'Booking Host Modify',
        amount: Math.abs(difference),
        status: 'Pending',
        method: null,
      });
    }

    res.json({
      success: true,
      message:
        'Booking modified successfully' +
        (difference < 0
          ? ` and refund of ${Math.abs(modifyData.total_amount_difference)} is on the way.`
          : ''),
      invalidDiscount: booking.discount_code ? invalidDiscount : undefined,
      data: {
        grandTotal: grand_total,
        oldTotalAmount: modifyData.old_total_amount,
        additionalPayment,
        refundAmount:
          modifyData.total_amount_difference < 0 ? Math.abs(modifyData.total_amount_difference) : 0,
      },
    });

    try {
      await sendEmail(
        modifiedBookingEmail(booking.email, {
          user_name: booking.fullname,
          [paxDifference > 0 ? 'added' : 'removed']: [
            `Guest ${paxDifference > 0 ? '+' : '-'} ${Math.abs(paxDifference)}`,
          ],
        }),
      );
    } catch (error) {
      console.log(error);
    }
  } catch (error) {
    next(error);
  }
};
