const { Op } = require('sequelize');
const {
  Bookings,
  Listings,
  Users,
  ListingSpots,
  ListingDiscounts,
  Discounts,
  ListingImages,
  ListingReviews,
  UserDiscounts,
  Transactions,
  BookingValidIds,
  sequelize,
  HostReviews,
  BookingAdditionalPayments,
} = require('../../../models');
const {
  cancelledBookingEmail,
  sendEmail,
  rebookingEmail,
  modifiedBookingEmail,
  modificationResponseEmail,
} = require('../../../services/emailService');
const { deleteFile, validatePax, parseJsonSafe } = require('../../../helpers');
const {
  generateReferenceNumber,
  errorHandler,
  paginate,
  formatTimestamp,
} = require('../../../util');
const {
  getListingActiveBookings,
  calculateInitialAmounts,
  calculateIncomes,
  syncPayment,
} = require('../../../helpers/booking-helper');
const {
  formatCaseDeep,
  formatImage,
  formatAddress,
  formatPaymentDetails,
  formatJsonLog,
} = require('../../../util/formatter');

const {
  parseAmount,
  checkoutSession,
  refundPayment,
  getPaymentStatus,
} = require('../../../services/payMongo');
const logger = require('../../../util/logger');
const { findOrCreateConversation, saveMessage } = require('../../../services/chatSystemServices');

function generateReturnUrl({
  success,
  returnUrl,
  bookingNo,
  listingId,
  booking,
  listing,
  listingSpot,
  guestFee,
  discount,
  subtotal,
  totalPayment,
  user,
}) {
  if (returnUrl === 'app') {
    const params = new URLSearchParams({
      success: String(success),
      bookingNo: bookingNo || '',
      status: listing.approval === 'Auto' ? 'Upcoming' : 'For Approval',
      listing: listing?.name || '',
      guestName: booking?.fullname || '',
      listingType: listing?.listing_type || '',
      spot: listingSpot?.name || '',
      dateCreated: booking?.createdAt
        ? new Date(booking.createdAt).toLocaleString('en-US', {
            hour12: true,
            timeZone: 'Asia/Manila',
          })
        : '',
      checkIn: booking?.check_in
        ? new Date(booking.check_in).toLocaleDateString('en-US', {
            year: 'numeric',
            month: 'long',
            day: 'numeric',
            timeZone: 'Asia/Manila',
          })
        : '',
      checkOut: booking?.check_out
        ? new Date(booking.check_out).toLocaleDateString('en-US', {
            year: 'numeric',
            month: 'long',
            day: 'numeric',
            timeZone: 'Asia/Manila',
          })
        : '',
      paymentStatus: success ? 'Paid' : 'Unpaid',
      price: listing?.price ?? '',
      guestFee: guestFee ?? '',
      discount: discount ?? '',
      paymentMethod: 'online',
      subtotal: subtotal ?? '',
      totalPayment: totalPayment ?? '',
      address:
        user?.address_city && user?.address_country
          ? `${user.address_city}, ${user.address_country}`
          : '',
      userImage: user?.image ? formatImage(user.image, 'user-uploads') : '',
      firstname: user?.firstname || '',
      lastname: user?.lastname || '',
    });

    return `${process.env.FRONTEND_URL}/bookings/app/?${params.toString()}`;
  }

  if (success) {
    return returnUrl
      ? `${returnUrl}?success=true&bookingNo=${bookingNo}`
      : `${process.env.FRONTEND_URL}/booking/${listingId}/successful-booking?referenceNo=${bookingNo}`;
  }

  return returnUrl
    ? `${returnUrl}?success=false&bookingNo=${bookingNo}`
    : `${process.env.FRONTEND_URL}/reservations?status=Unpaid`;
}

exports.createBooking = async (req, res) => {
  const { userId } = req;
  const { data } = req.body;
  const {
    listingId,
    listingSpotId,
    checkIn,
    checkOut,
    pax,
    returnUrl = process.env.FRONTEND_URL,
    messageToHost,
    fullname,
    email,
    phone,
    discountCode,
  } = data;

  const { travelersPhoto, governmentId } = req.files;

  let transaction;

  try {
    const listing = await Listings.findByPk(listingId, {
      include: [
        { model: ListingSpots, as: 'listingSpots' },
        { model: ListingDiscounts, as: 'discounts' },
      ],
    });

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

    // const activeBookings = await getListingActiveBookings(
    // 	listingId,
    // 	checkIn,
    // 	checkOut,
    // 	listing.listing_type === 'Public' ? listingSpotId : null,
    // );

    // if (activeBookings.length > 0) {
    // 	errorHandler(
    // 		`Listing ${listing.listing_type == 'Public' ? 'Spot ' : ''}is fully booked for the selected date range, ${listing.listing_type == 'Public' ? 'you may try to use different spot.' : ''}`,
    // 		400,
    // 	);
    // }

    let listingSpot = null;
    if (listing.listing_type === 'Public') {
      if (!listingSpotId) {
        errorHandler('Listing spot id is required for public bookings', 400);
      }

      listingSpot = listing.listingSpots.find((spot) => spot.id === listingSpotId);

      if (!listingSpot) {
        errorHandler('Listing spot not found', 404);
      }
    }

    validatePax(pax, listing.max_capacity);
    const bookingNo = generateReferenceNumber();

    const price = listing.price;
    const hostId = listing.host_id;

    const { initial_guest_fee, sub_total } = calculateInitialAmounts(price, checkIn, checkOut);

    let discount = 0,
      discountData;

    if (discountCode) {
      const handledDiscount = await handleDiscount(discountCode, listing, sub_total, pax, userId);
      discount = handledDiscount.discount;
      discountData = handledDiscount.discountData;
    }

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

    let user_image = null;
    if (travelersPhoto?.[0]) {
      user_image = travelersPhoto[0].filename;
    }

    transaction = await sequelize.transaction();
    const newBooking = await Bookings.create(
      {
        customer_id: userId,
        host_id: hostId,
        listing_id: listingId,
        booking_no: bookingNo,
        check_in: checkIn,
        check_out: checkOut,
        pax,
        payment_status: 'Unpaid',
        price_per_night: price,
        subtotal: sub_total,
        total_amount: grand_total,
        balance: balance,
        user_image,
        listing_spot_id: listing.listing_type === 'Public' ? listingSpotId : null,
        status: 'Unpaid',
        message_to_host: messageToHost,
        phone_no: phone,
        fullname,
        email,
        guest_fee,
        host_fee,
        total_host_income,
        aircamp_income,
        ...discountData,
      },
      { transaction },
    );

    const user = await Users.findByPk(userId, {
      attributes: [
        'id',
        'firstname',
        'lastname',
        'email_address',
        'image',
        'address_city',
        'address_country',
        'valid_id_image',
      ],
    });

    // Validate government ID is uploaded before proceeding to payment
    const hasNewGovernmentId = governmentId?.[0]?.filename;
    const hasExistingGovernmentId = user?.valid_id_image;

    if (!hasNewGovernmentId && !hasExistingGovernmentId) {
      await transaction.rollback();

      // Check if upload failed
      if (req.uploadError) {
        const { type, message, canRetry } = req.uploadError;
        return res.status(400).json({
          success: false,
          error: {
            message: 'Government ID upload failed. Please try again.',
            type,
            uploadError: message,
            canRetry,
          },
        });
      }

      // No government ID provided at all
      return res.status(400).json({
        success: false,
        error: {
          message:
            'Government ID is required to proceed with booking. Please upload a photo of your government-issued ID.',
          type: 'MISSING_GOVERNMENT_ID',
          field: 'governmentId',
          canRetry: false,
        },
      });
    }

    if (hasNewGovernmentId) {
      user.valid_id_image = governmentId[0].filename;
      await user.save({ transaction });
    }

    await Transactions.create(
      {
        booking_id: newBooking.id,
        amount: grand_total,
        action: 'Booking',
        type: 'Payment',
        status: 'Pending',
        description: `Payment for booking ${bookingNo}`,
      },
      { transaction },
    );

    const paymentDataRaw = await checkoutSession({
      amount: grand_total,
      currency: 'PHP',
      description:
        `AirCamp Booking ${bookingNo} - ${listing.name}` +
        (listingSpotId ? ` - ${listingSpot.name}` : ''),
      bookingNo,
      successUrl: generateReturnUrl({
        returnUrl,
        bookingNo,
        listingId,
        success: true,
        booking: newBooking,
        listing,
        listingSpot,
        guestFee: guest_fee,
        discount,
        subtotal: sub_total,
        totalPayment: grand_total,
        user,
      }),
      failUrl: generateReturnUrl({
        returnUrl,
        bookingNo,
        listingId,
        success: false,
        booking: newBooking,
        listing,
        listingSpot,
        guestFee: guest_fee,
        discount,
        subtotal: sub_total,
        totalPayment: grand_total,
        user,
      }),
    });

    if (paymentDataRaw.error) {
      await transaction.rollback();
      return res.status(500).json({ error: paymentDataRaw.error });
    }

    const paymentData = {
      payment_intent_id: paymentDataRaw.payment_intent_id ?? null,
      payment_url: paymentDataRaw.payment_url,
    };

    await newBooking.update(
      {
        payment_intent_id: paymentData.payment_intent_id,
      },
      { transaction },
    );

    await transaction.commit();

    // Log payment URL for debugging
    logger.info(
      `Payment checkout created for booking ${bookingNo}. URL: ${paymentData.payment_url}`,
    );

    // Set proper headers for payment response
    res.setHeader('Content-Type', 'application/json');
    res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');

    res.status(201).json({
      success: true,
      message: 'Booking created successfully. Please complete payment.',
      data: {
        bookingId: newBooking.id,
        bookingNo,
        paymentUrl: paymentData.payment_url,
        amount: grand_total,
        redirectUrl: paymentData.payment_url, // Alternative field name for clarity
      },
      successUrl: generateReturnUrl({
        returnUrl,
        bookingNo,
        listingId,
        success: true,
        booking: newBooking,
        listing,
        listingSpot,
        guestFee: guest_fee,
        discount,
        subtotal: sub_total,
        totalPayment: grand_total,
        user,
      }),
      failUrl: generateReturnUrl({
        returnUrl,
        bookingNo,
        listingId,
        success: false,
        booking: newBooking,
        listing,
        listingSpot,
        guestFee: guest_fee,
        discount,
        subtotal: sub_total,
        totalPayment: grand_total,
        user,
      }),
    });
  } catch (error) {
    logger.error(
      'Create booking with payment error:' +
        JSON.stringify(
          {
            message: error?.data?.message || error.message || 'Unknown error',
            type: error?.type,
            canRetry: error?.canRetry,
            stack: error?.stack,
            error,
          },
          null,
          2,
        ),
    );
    if (transaction) await transaction.rollback();

    if (travelersPhoto) deleteFile(travelersPhoto);
    if (governmentId) deleteFile(governmentId);

    // Return enhanced error information if available from PayMongo service
    const statusCode = error?.statusCode || 500;
    const errorResponse = {
      success: false,
      message: error.message || 'Failed to create booking',
    };

    // Include additional error details if available
    if (error?.type) {
      errorResponse.error = {
        type: error.type,
        canRetry: error.canRetry !== undefined ? error.canRetry : true,
      };
    }

    res.status(statusCode).json(errorResponse);
  }
};

exports.retryBookingPayment = async (req, res, next) => {
  const { userId } = req;
  const { paymentMethod, returnUrl, bookingId } = req.body;

  let transaction;
  try {
    const user = await Users.findByPk(userId, {
      attributes: [
        'id',
        'firstname',
        'lastname',
        'email_address',
        'image',
        'address_city',
        'address_country',
        'valid_id_image',
      ],
    });

    // Validate government ID exists before retrying payment
    if (!user?.valid_id_image) {
      return res.status(400).json({
        success: false,
        error: {
          message:
            'Government ID is required to proceed with booking. Please upload a photo of your government-issued ID before retrying payment.',
          type: 'MISSING_GOVERNMENT_ID',
          field: 'governmentId',
          canRetry: false,
        },
      });
    }

    const booking = await Bookings.findOne({
      where: {
        id: bookingId,
        customer_id: userId,
      },
      include: [
        {
          model: Listings,
          as: 'listing',
          attributes: ['id', 'name'],
        },
        {
          model: ListingSpots,
          as: 'spot',
          attributes: ['id', 'name'],
        },
      ],
    });

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

    if (booking.payment_status === 'Fully Paid') {
      return res.status(400).json({
        success: false,
        message: 'Payment has already been completed.',
        refresh: true,
      });
    }

    const paymongoStatus = await getPaymentStatus(booking.payment_intent_id);

    if (paymongoStatus?.attributes?.status === 'succeeded') {
      // Only update if not already paid to avoid race conditions with webhook
      if (booking.payment_status !== 'Fully Paid') {
        await booking.update({
          payment_status: 'Fully Paid',
          status: booking.listing?.approval === 'Auto' ? 'Upcoming' : 'For Approval',
          amount_paid: (paymongoStatus?.attributes?.payments?.[0]?.attributes?.amount || 0) / 100,
          balance: 0,
          paymongo_payment_id: paymongoStatus?.attributes?.payments?.[0]?.id || null,
          paymongo_fee: (paymongoStatus?.attributes?.payments?.[0]?.attributes?.fee || 0) / 100,
          payment_method:
            paymongoStatus?.attributes?.payments?.[0]?.attributes?.source?.type || 'unknown',
        });

        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,
          );
        }
      }

      return res.status(200).json({
        success: true,
        message: 'Payment completed successfully.',
        refresh: true,
      });
    }

    transaction = await sequelize.transaction();
    if (booking.status === 'Unpaid') {
      const createdAt = new Date(booking.createdAt);
      const now = new Date();
      const diffMinutes = (now.getTime() - createdAt.getTime()) / (1000 * 60);

      if (diffMinutes > 30) {
        await booking.update(
          {
            status: 'Cancelled',
            payment_status: 'Unpaid',
            cancellation_reason: 'Automatically cancelled due to non-payment after 30 minutes.',
          },
          { transaction },
        );

        await transaction.commit();
        return res.status(400).json({
          success: false,
          message: 'Booking automatically cancelled due to non-payment after 30 minutes.',
        });
      }
    }

    const paymentDataRaw = await checkoutSession({
      amount: booking.total_amount,
      currency: 'PHP',
      description:
        `AirCamp Booking ${booking.booking_no} - ${booking.listing.name}` +
        (booking.listing_spot_id ? ` - ${booking.spot.name}` : ''),
      bookingNo: booking.booking_no,
      successUrl: generateReturnUrl({
        returnUrl,
        bookingNo: booking.booking_no,
        listingId: booking.listing.id,
        success: true,
        booking: booking,
        listing: booking.listing,
        listingSpot: booking.spot,
        guestFee: booking.guest_fee,
        discount: booking.discount_amount,
        subtotal: booking.subtotal,
        totalPayment: booking.total_amount,
        user,
      }),
      failUrl: generateReturnUrl({
        returnUrl,
        bookingNo: booking.booking_no,
        listingId: booking.listing.id,
        success: false,
        booking: booking,
        listing: booking.listing,
        listingSpot: booking.spot,
        guestFee: booking.guest_fee,
        discount: booking.discount_amount,
        subtotal: booking.subtotal,
        totalPayment: booking.total_amount,
        user,
      }),
    });

    if (paymentDataRaw.error) {
      await transaction.rollback();
      return res.status(500).json({
        success: false,
        message: paymentDataRaw.message,
        error: paymentDataRaw.error,
      });
    }

    const paymentData = {
      payment_intent_id: paymentDataRaw.payment_intent_id ?? null,
      payment_url: paymentDataRaw.payment_url,
    };

    await booking.update(
      {
        payment_intent_id: paymentData.payment_intent_id,
        updatedAt: new Date(),
      },
      { transaction },
    );

    await Transactions.update(
      {
        status: 'Pending',
        method: paymentMethod || booking.payment_method,
        updatedAt: new Date(),
      },
      {
        where: {
          booking_id: booking.id,
          type: 'Payment',
          status: { [Op.ne]: 'Paid' },
        },
      },
      { transaction },
    );

    await transaction.commit();

    // Set proper headers for payment response
    res.setHeader('Content-Type', 'application/json');
    res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');

    res.status(201).json({
      success: true,
      message: 'Booking created successfully. Please complete payment.',
      data: {
        bookingId: booking.id,
        bookingNo: booking.booking_no,
        paymentUrl: paymentData.payment_url,
        amount: booking.total_amount,
      },
    });
  } catch (error) {
    if (transaction) await transaction.rollback();
    logger.error(
      'Retry booking payment error:' +
        formatJsonLog({
          error: error?.response?.data || error.message || 'Unknown error',
          type: error?.type,
          canRetry: error?.canRetry,
          stack: error.stack,
        }),
    );

    // Return enhanced error information if available from PayMongo service
    const statusCode = error?.statusCode || 500;
    const errorResponse = {
      success: false,
      message: error.message || 'Failed to generate new payment link',
    };

    // Include additional error details if available
    if (error?.type) {
      errorResponse.error = {
        type: error.type,
        canRetry: error.canRetry !== undefined ? error.canRetry : true,
      };
    }

    res.status(statusCode).json(errorResponse);
  }
};

exports.checkBookingPayments = async (req, res, next) => {
  try {
    const { userId } = req;
    const { data } = req.body;
    const { listingId, checkIn, checkOut, pax, discountCode, listingSpotId } = data;

    const listing = await Listings.findOne({
      where: { id: listingId, status: 'Approved' },
    });

    if (!listing) {
      errorHandler('Listing not found', 404);
    }

    let listingSpot = null;
    if (listing.listing_type === 'Public') {
      if (!listingSpotId) {
        errorHandler('Listing spot id is required for public bookings', 400);
      }

      listingSpot = await ListingSpots.findOne({
        where: { id: listingSpotId, listing_id: listingId },
      });

      if (!listingSpot) {
        errorHandler('Listing spot not found', 404);
      }
    }

    const activeBookings = await getListingActiveBookings(
      listingId,
      checkIn,
      checkOut,
      listing.listing_type === 'Public' ? listingSpotId : null,
    );

    if (activeBookings.length > 0) {
      errorHandler(
        `Listing ${listing.listing_type == 'Public' ? 'Spot ' : ''}is fully booked for the selected date range, ${listing.listing_type == 'Public' ? 'you may try to use different spot.' : ''}`,
        400,
      );
    }

    validatePax(pax, listing.max_capacity);

    const price = listing.price;
    const hostId = listing.host_id;

    const { initial_amount, initial_guest_fee, sub_total } = calculateInitialAmounts(
      price,
      checkIn,
      checkOut,
    );

    let discount = 0,
      discountData;

    if (discountCode) {
      const handledDiscount = await handleDiscount(discountCode, listing, sub_total, pax, userId);
      discount = handledDiscount.discount;
      discountData = handledDiscount.discountData;
    }

    const { grand_total, guest_fee, balance } = calculateIncomes(
      sub_total,
      initial_guest_fee,
      discount,
    );

    let bookingData = {
      hostId,
      listingId,
      checkIn,
      checkOut,
      pax,
      price,
      initialAmount: initial_amount,
      guestFee: guest_fee,
      subTotal: sub_total,
      grandTotal: grand_total,
      balance,
      discountData,
    };

    bookingData = formatCaseDeep(bookingData);

    res.status(200).json({
      success: true,
      message: 'Booking calculated successfully',
      data: bookingData,
    });
  } catch (error) {
    next(error);
  }
};

exports.getAllMyBookings = (req, res, next) => {
  const { userId } = req;
  const { status, search, startDate, endDate, page, limit } = req.query;

  const filter = {
    customer_id: userId,
  };

  if (status) {
    filter.status = status;
  }

  if (startDate) {
    filter[Op.and] = [
      {
        [Op.or]: [{ check_in: { [Op.gte]: startDate } }, { check_out: { [Op.gte]: startDate } }],
      },
    ];
  }

  if (endDate) {
    const endOfDay = new Date(endDate);
    endOfDay.setHours(23, 59, 59, 999);

    if (!filter[Op.and]) {
      filter[Op.and] = [];
    }
    filter[Op.and].push({
      [Op.or]: [{ check_in: { [Op.lte]: endDate } }, { check_out: { [Op.lte]: endDate } }],
    });
  }

  const listingFilter = {};

  if (search) {
    listingFilter[Op.or] = [
      {
        name: { [Op.like]: `%${search}%` },
      },
      {
        location: { [Op.like]: `%${search}%` },
      },
      {
        address_city: { [Op.like]: `%${search}%` },
      },
      {
        address_country: { [Op.like]: `%${search}%` },
      },
    ];
  }

  Bookings.findAll({
    where: filter,
    include: [
      {
        model: Listings,
        as: 'listing',
        attributes: ['id', 'name', 'price', 'location', 'address_city', 'address_country'],
        include: [
          {
            model: ListingImages,
            as: 'listingImages',
            attributes: ['image'],
            limit: 1,
          },
        ],
        where: listingFilter,
      },
    ],
    attributes: [
      'id',
      'booking_no',
      'check_in',
      'check_out',
      'status',
      'payment_status',
      'createdAt',
      'updatedAt',
      'payment_intent_id',
      'paymongo_payment_id',
    ],
    order: [['createdAt', 'DESC']],
  })
    .then(async (bookings) => {
      const bookingsToUpdate = [];
      const mappedBookings = [];

      for (const item of bookings) {
        const booking = item?.get({ plain: true });

        const listingImage = formatImage(booking.listing?.listingImages?.[0]?.image);

        const listing = formatAddress(booking.listing);
        delete listing.listingImages;

        if (
          ['Ongoing', 'Upcoming'].includes(booking.status) &&
          new Date(booking.check_out) < new Date() &&
          booking.payment_status === 'Fully Paid'
        ) {
          bookingsToUpdate.push(booking.id);
          booking.status = 'Done';
        }

        mappedBookings.push({
          id: booking.id,
          bookingNo: booking.booking_no,
          checkIn: booking.check_in,
          checkOut: booking.check_out,
          listingSpotId: booking.listing_spot_id,
          status: booking.status,
          paymentStatus: booking.payment_status,
          listing: {
            ...listing,
            id: booking.listing.id,
            image: listingImage,
            name: booking?.listing?.name ? booking.listing.name : 'N/A',
            description: booking?.listing?.description ? booking.listing.description : 'N/A',
          },
          createdAt: booking.createdAt,
          updatedAt: booking.updatedAt,
          payment_intent_id: booking.payment_intent_id,
          paymongo_payment_id: booking.paymongo_payment_id,
        });
      }

      if (bookingsToUpdate.length > 0) {
        await Bookings.update({ status: 'Done' }, { where: { id: { [Op.in]: bookingsToUpdate } } });
      }

      if (!page || !limit) {
        return res.status(200).json({
          success: true,
          data: mappedBookings,
        });
      }

      const paginated = paginate(mappedBookings, parseInt(page), parseInt(limit));
      return res.status(200).json({
        success: true,
        ...paginated,
      });
    })
    .catch((err) => {
      next(err);
    });
};

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

    let booking = await getBookingDetails(bookingNumber, userId);

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

    // if (booking.status === 'Unpaid' || booking.payment_status !== 'Fully Paid') {
    // 	await syncPayment(bookingNumber);
    // 	booking = await getBookingDetails(bookingNumber, userId);
    // }

    booking = booking.get({ plain: true });
    booking = formatPaymentDetails(booking);

    booking.listing.image = formatImage(booking?.listing?.listingImages?.[0]?.image);
    booking.government_id = formatImage(booking.government_id, 'user-uploads');

    delete booking?.listing?.listingImages;

    const listing = formatAddress(booking.listing);
    listing.amenities = parseJsonSafe(booking.listing.amenities);

    delete booking.listing;

    booking.listing = listing;
    booking.updates_data = parseJsonSafe(booking.updates_data);

    booking = formatCaseDeep(booking);

    for (const key of ['createdAt', 'updatedAt']) {
      booking[key] = formatTimestamp(booking[key]);
    }

    if (booking?.listing?.listingReviews?.length > 1) {
      const listingReviewStats = booking?.listing?.listingReviews?.reduce(
        (acc, { rating }) => {
          acc.totalRating += rating;
          acc.count += 1;
          return acc;
        },
        {
          totalRating: 0,
          reviewsCount: 0,
        },
      );
      booking.listing.averageRating =
        listingReviewStats.totalRating / listingReviewStats.reviewsCount || 0;
    } else {
      booking.listing.averageRating = null;
    }

    delete booking?.listing?.listingReviews;

    return res.json({
      message: true,
      data: booking,
    });
  } catch (error) {
    next(error);
  }
};

async function getBookingDetails(bookingNumber, userId) {
  const booking = await Bookings.findOne({
    where: {
      booking_no: bookingNumber,
      customer_id: userId,
    },
    attributes: {
      exclude: [
        'customer_id',
        'host_id',
        'listing_id',
        'guest_fee',
        'cleaning_fee',
        'balance',
        'amount_paid',
        'price_per_night',
        'host_fee',
        'total_host_income',
        'aircamp_income',
        'discount_code',
        'discount_type',
        'discount_percentage',
        'discount_num_of_pax',
        'discount_amount_to_reach',
      ],
    },
    include: [
      {
        model: Listings,
        as: 'listing',
        // attributes: [
        // 	'id',
        // 	'name',
        // 	'description',
        // 	'address_city',
        // 	'address_country',
        // 	'address_apt',
        // 	'address_zip',
        // 	'address_street',
        // 	'location',
        // 	'price',
        // 	'amenities',
        // 	'listing_type',
        // 	'longitude',
        // 	'latitude',
        // ],
        include: [
          {
            model: ListingImages,
            as: 'listingImages',
            attributes: ['image'],
            limit: 1,
          },
          {
            model: ListingReviews,
            as: 'listingReviews',
            attributes: ['rating'],
          },
          {
            model: Users,
            as: 'host',
            attributes: ['id', 'firstname', 'middlename', 'lastname'],
          },
        ],
      },
      {
        model: ListingSpots,
        as: 'spot',
        attributes: ['name', 'inclusions'],
      },
      {
        model: ListingReviews,
        as: 'listingReview',
        attributes: ['id', 'content', 'rating', 'createdAt'],
      },
      {
        model: HostReviews,
        as: 'hostReview',
        attributes: ['id', 'content', 'rating', 'createdAt'],
      },
    ],
  });

  return booking;
}

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

  let transaction;

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

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

    const paymongoStatus = await getPaymentStatus(booking.payment_intent_id);

    if (paymongoStatus?.attributes?.status === 'succeeded') {
      await booking.update({
        payment_status: 'Fully Paid',
        status: booking.listing?.approval === 'Auto' ? 'Upcoming' : 'For Approval',
        amount_paid: (paymongoStatus?.attributes?.payments?.[0]?.attributes?.amount || 0) / 100,
        balance: 0,
        paymongo_payment_id: paymongoStatus?.attributes?.payments?.[0]?.id || null,
        paymongo_fee: (paymongoStatus?.attributes?.payments?.[0]?.attributes?.fee || 0) / 100,
        payment_method:
          paymongoStatus?.attributes?.payments?.[0]?.attributes?.source?.type || 'unknown',
      });

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

    if (booking.customer_id !== userId) {
      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 && booking.payment_status === 'Fully Paid') {
      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: 'Customer',
        refund_percentage: refundPercentage,
        guest_fee: booking.guest_fee,
        host_fee: booking.host_fee,
        host_income: booking.total_host_income,
        aircamp_income: booking.aircamp_income,
      },
    });

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

    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;

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

    let refundError, refundedAmount;
    if (
      !booking.is_rescheduled &&
      refundPercentage > 0 &&
      booking.payment_status === 'Fully Paid'
    ) {
      const refunded = await refundPayment(
        booking.paymongo_payment_id,
        total_refund,
        booking.paymongo_fee,
        'Cancellation by Customer : ' + reason,
      );

      if (refunded.error) {
        refundError = refunded.error;
      } else if (refunded && !refunded.error) {
        refundedAmount = parseAmount(refunded.attributes.amount);

        booking.is_refunded = true;
        booking.refund_amount = parseAmount(refunded.attributes.amount);
        booking.refunded_at = new Date();
        booking.paymongo_refund_id = refunded.id;
      }

      await Transactions.create(
        {
          booking_id: booking.id,
          host_id: booking.host_id,
          customer_id: userId,
          type: 'Refund',
          action: 'Booking Cancellation',
          amount: refundedAmount,
          status: refunded ? 'Paid' : 'Pending',
          method: null,
        },
        { transaction },
      );
    }

    await booking.save({ 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 if (refundPercentage > 0) {
      if (booking.is_refunded) {
        message = `Booking cancelled successfully. Refund: ${refundPercentage}% has been processed.`;
      } else {
        message = `Booking cancelled successfully. Refund: ${refundPercentage}% will be processed.`;
      }
    } else {
      message = 'Booking cancelled successfully. No refund is applicable.';
    }

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

    try {
      const listing = await Listings.findByPk(booking.listing_id);
      const user = await Users.findByPk(userId, {
        attributes: ['id', 'firstname', 'lastname', 'email_address', 'image'],
      });
      const user_name = user.firstname;

      const data = {
        user_name,
        listing_id: listing.id,
      };

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

      //HOST EMAIL
      // const host = await Users.findByPk(listing.host_id);
      // const hostEmail = host.email_address;
      // const host_name = host.firstname;

      // await sendEmail(
      // 	cancelledBookingHostEmail(hostEmail, {
      // 		user_full_name: `${user.firstname} ${user.lastname}`,
      // 		host_name,
      // 		...bookingDetails,
      // 	}),
      // );
    } catch (error) {
      logger.error('Error sending cancellation email:', error);
    }
  } catch (error) {
    if (transaction) await transaction.rollback();
    next(error);
  }
};

exports.rebook = async (req, res, next) => {
  try {
    const { userId } = req;
    const {
      bookingNo,
      newCheckOut,
      newCheckIn,
      pax,
      returnUrl = process.env.FRONTEND_URL,
    } = req.body;

    const user = await Users.findByPk(userId, {
      attributes: [
        'id',
        'firstname',
        'lastname',
        'email_address',
        'image',
        'address_city',
        'address_country',
      ],
    });

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

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

    if (booking.status !== 'Cancelled') {
      return errorHandler('Booking must be cancelled before rebooking', 400);
    }

    if (booking.customer_id !== userId) {
      return errorHandler('You are not authorized to rebook this booking', 403);
    }

    if (booking?.is_rescheduled) {
      return errorHandler('Booking has already been rebooked', 400);
    }

    if (newCheckIn === booking.check_in && newCheckOut === booking.check_out) {
      return errorHandler('New dates are the same as the current booking dates', 400);
    }

    const listing = await Listings.findByPk(booking.listing_id, {
      include: [
        { model: ListingSpots, as: 'listingSpots' },
        { model: ListingDiscounts, as: 'discounts' },
      ],
    });

    let listingSpot = null;
    if (listing.listing_type === 'Public') {
      if (!booking.listing_spot_id) {
        errorHandler('Listing spot id is required for public bookings', 400);
      }
      listingSpot = listing.listingSpots.find((spot) => spot.id === booking.listing_spot_id);
      if (!listingSpot) {
        errorHandler('Listing spot not found', 404);
      }
    }

    const activeBookings = await getListingActiveBookings(
      listing.id,
      newCheckIn,
      newCheckOut,
      listing.listing_type === 'Public' ? booking.listing_spot_id : null,
      booking.id,
    );

    if (activeBookings.length > 0) {
      errorHandler(
        `Listing ${listing.listing_type == 'Public' ? 'Spot ' : ''}is fully booked for the selected date range, ${listing.listing_type == 'Public' ? 'you may try to use different spot.' : ''}`,
        400,
      );
    }

    const checkInDate = new Date(newCheckIn);
    const today = new Date();
    const diffInDays = Math.ceil((checkInDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));

    let requiredRebookFee = 0;

    if (diffInDays >= 15) {
      requiredRebookFee = 0;
    } else if (diffInDays >= 8) {
      requiredRebookFee = booking.total_amount * 0.2;
    } else if (diffInDays >= 4) {
      requiredRebookFee = booking.total_amount * 0.5;
    } else {
      return errorHandler('Rebooking not allowed for 3 days or less before check-in', 400);
    }

    const { initial_guest_fee, sub_total } = calculateInitialAmounts(
      listing.price,
      newCheckIn,
      newCheckOut,
    );

    let discount = 0,
      discountData;

    if (booking.discount_code) {
      const handledDiscount = await handleDiscount(
        booking.discount_code,
        listing,
        sub_total,
        pax,
        userId,
      );
      discount = handledDiscount.discount;
      discountData = handledDiscount.discountData;
    }

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

    const newBookingNo = generateReferenceNumber();
    const newBookingData = {
      customer_id: booking.customer_id,
      host_id: booking.host_id,
      listing_id: booking.listing_id,
      booking_no: newBookingNo,
      check_in: newCheckIn,
      check_out: newCheckOut,
      pax: pax,
      payment_status: 'Unpaid',
      price_per_night: listing.price,
      subtotal: sub_total,
      total_amount: grand_total,
      amount_paid: 0,
      balance: balance,
      user_image: booking.user_image,
      listing_spot_id: booking.listing_spot_id,
      status: listing.approval === 'Auto' ? 'Upcoming' : 'For Approval',
      message_to_host: booking.message_to_host,
      phone_no: booking.phone_no,
      fullname: booking.fullname,
      email: booking.email,
      guest_fee,
      host_fee,
      total_host_income,
      aircamp_income,
      is_rescheduled: false,
      ...discountData,
    };

    const transaction = await sequelize.transaction();
    let newBooking;
    try {
      newBooking = await Bookings.create(newBookingData, { transaction });

      booking.is_rescheduled = true;
      await booking.save({ transaction });

      await Transactions.create(
        {
          host_id: newBooking.host_id,
          booking_id: newBooking.id,
          customer_id: userId,
          type: 'Payment',
          action: 'Booking',
          amount: grand_total,
          status: 'Pending',
          description: `Payment for booking ${newBookingNo}`,
        },
        { transaction },
      );

      const paymentDataRaw = await checkoutSession({
        amount: grand_total,
        currency: 'PHP',
        description:
          `AirCamp Booking ${newBookingNo} - ${listing.name}` +
          (booking.listing_spot_id ? ` - ${listingSpot?.name}` : ''),
        bookingNo: newBookingNo,
        successUrl: generateReturnUrl({
          returnUrl,
          bookingNo,
          listingId: listing.id,
          success: true,
          booking: newBooking,
          listing,
          listingSpot,
          guestFee: guest_fee,
          discount,
          subtotal: sub_total,
          totalPayment: grand_total,
          user,
        }),
        failUrl: generateReturnUrl({
          returnUrl,
          bookingNo,
          listingId: listing.id,
          success: false,
          booking: newBooking,
          listing,
          listingSpot,
          guestFee: guest_fee,
          discount,
          subtotal: sub_total,
          totalPayment: grand_total,
          user,
        }),
      });

      if (paymentDataRaw.error) {
        await transaction.rollback();
        return res.status(500).json({ error: paymentDataRaw.error });
      }

      const paymentData = {
        payment_intent_id: paymentDataRaw.payment_intent_id ?? null,
        payment_url: paymentDataRaw.payment_url,
      };

      await newBooking.update(
        {
          payment_intent_id: paymentData.payment_intent_id,
        },
        { transaction },
      );

      await transaction.commit();

      res.status(201).json({
        success: true,
        message: 'Booking rescheduled successfully. Please complete payment.',
        data: {
          bookingId: newBooking.id,
          bookingNo: newBookingNo,
          paymentUrl: paymentData.payment_url,
          amount: grand_total,
        },
      });
    } catch (err) {
      await transaction.rollback();
      return next(err);
    }

    try {
      await sendEmail(
        rebookingEmail(booking.email, {
          user_name: booking.fullname,
          reference_no: newBookingNo,
          listing_type: listing.listing_type,
          new_booking_date: `${new Date(newCheckIn).toLocaleString('en-US', {
            month: 'long',
            day: 'numeric',
            year: 'numeric',
          })} - ${new Date(newCheckOut).toLocaleString('en-US', {
            month: 'long',
            day: 'numeric',
            year: 'numeric',
          })}`,
        }),
      );
    } catch (error) {
      logger.error(
        'Send email error:',
        error?.data?.message || error.message || 'Unknown error',
        error,
      );
    }
  } catch (error) {
    next(error);
  }
};

exports.checkRebook = async (req, res, next) => {
  try {
    const { userId } = req;
    const { bookingNo, newCheckOut, newCheckIn } = req.body;

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

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

    if (booking.status !== 'Cancelled') {
      return errorHandler('Booking must be cancelled before rebooking', 400);
    }

    if (booking.customer_id !== userId) {
      return errorHandler('You are not authorized to rebook this booking', 403);
    }

    if (booking?.is_rescheduled) {
      return errorHandler('Booking has already been rebooked', 400);
    }

    if (newCheckIn === booking.check_in && newCheckOut === booking.check_out) {
      return errorHandler('New dates are the same as the current booking dates', 400);
    }

    const listing = await Listings.findByPk(booking.listing_id);

    const activeBookings = await getListingActiveBookings(
      listing.id,
      newCheckIn,
      newCheckOut,
      listing.listing_type === 'Public' ? booking.listing_spot_id : null,
      booking.id,
    );

    if (activeBookings.length > 0) {
      errorHandler(
        `Listing ${listing.listing_type == 'Public' ? 'Spot ' : ''}is fully booked for the selected date range, ${listing.listing_type == 'Public' ? 'you may try to use different spot.' : ''}`,
        400,
      );
    }

    const checkInDate = new Date(newCheckIn);
    const today = new Date();
    const diffInDays = Math.ceil((checkInDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));

    let requiredRebookFee = 0;

    if (diffInDays >= 15) {
      requiredRebookFee = 0;
    } else if (diffInDays >= 8) {
      requiredRebookFee = booking.total_amount * 0.2;
    } else if (diffInDays >= 4) {
      requiredRebookFee = booking.total_amount * 0.5;
    } else {
      return errorHandler('Rebooking not allowed for 3 days or less before check-in', 400);
    }

    const { initial_guest_fee, sub_total } = calculateInitialAmounts(
      listing.price,
      newCheckIn,
      newCheckOut,
    );

    let discount = 0;

    const { grand_total, guest_fee } = calculateIncomes(
      sub_total,
      initial_guest_fee,
      0,
      discount,
      requiredRebookFee,
    );

    res.status(200).json({
      success: true,
      message: 'Booking is available for rebooking',
      data: {
        sub_total,
        guestFee: guest_fee,
        grandTotal: grand_total,
        requiredRebookFee,
        newCheckIn,
        newCheckOut,
      },
    });
  } catch (error) {
    next(error);
  }
};

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

    const booking = await Bookings.findOne({
      where: { booking_no: bookingNo },
      include: [{ model: ListingSpots, as: 'spot', attributes: ['name'] }],
    });

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

    if (booking.customer_id !== userId) {
      return errorHandler('You are not authorized to update this booking', 403);
    }

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

    if (booking.payment_status !== 'Fully Paid') {
      return errorHandler('Only fully paid bookings can be modified', 400);
    }

    if (booking.is_customer_modified) {
      return errorHandler('Booking can only be modified once by the customer', 400);
    }

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

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

    if (booking.pax === newPax) {
      return errorHandler('Pax is the same as the current booking', 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_balance: booking.balance,
      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,
          userId,
        );
        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);

    modifyData.old_balance = booking.balance;
    let additional_balance = additionalPayment > 0 ? additionalPayment : 0;
    let new_balance = Number(booking.balance) + Number(additional_balance);
    modifyData.current_balance = new_balance;

    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,
      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.balance = new_balance;
    booking.subtotal = sub_total;
    booking.customer_modify_additional_payment = additionalPayment > 0 ? additionalPayment : 0;

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

    let paymentIntentId = null;
    let paymentUrl = null;

    const difference = modifyData.total_amount_difference;

    let booking_additional_payment = null;
    if (difference > 0) {
      const user = await Users.findByPk(userId, {
        attributes: ['id', 'firstname', 'lastname', 'email_address', 'image'],
      });
      const paymentDataRaw = await checkoutSession({
        amount: difference,
        currency: 'PHP',
        description: `AirCamp Booking Modification ${booking.booking_no} - ${listing.name}`,
        bookingNo: booking.booking_no,
        successUrl: generateReturnUrl({
          returnUrl,
          bookingNo,
          listingId: booking.listing_id,
          success: true,
          booking: booking,
          listing,
          listingSpot: booking?.spot,
          guestFee: guest_fee,
          discount,
          subtotal: sub_total,
          totalPayment: grand_total,
          user,
        }),
        failUrl: generateReturnUrl({
          returnUrl,
          bookingNo,
          listingId: booking.listing_id,
          success: false,
          booking: booking,
          listing,
          listingSpot: booking?.spot,
          guestFee: guest_fee,
          discount,
          subtotal: sub_total,
          totalPayment: grand_total,
          user,
        }),
      });

      if (paymentDataRaw.error) {
        logger.error(
          'Payment error:' + paymentDataRaw.error + JSON.stringify(paymentDataRaw, null, 2),
        );
        return res.status(500).json({ success: false, message: 'Payment processing error' });
      }

      paymentIntentId = paymentDataRaw.payment_intent_id ?? null;
      paymentUrl = paymentDataRaw.payment_url;

      booking_additional_payment = await BookingAdditionalPayments.create({
        description: 'Additional payment for booking modification',
        booking_id: booking.id,
        amount: difference,
        payment_intent_id: paymentIntentId,
        status: 'Unpaid',
      });

      await Transactions.create({
        booking_id: booking.id,
        host_id: booking.host_id,
        customer_id: userId,
        type: 'Payment',
        action: 'Booking Modify',
        amount: difference,
        status: 'Pending',
        method: null,
        payment_intent_id: paymentIntentId,
        booking_additional_payment_id: booking_additional_payment.id,
      });

      booking.payment_status = 'Partially Paid';
      booking.customer_modify_payment_status = 'Unpaid';
      booking.customer_modify_payment = difference;
    } else if (difference < 0) {
      const refundAmount = Math.abs(difference);

      const refunded = await refundPayment(
        booking.paymongo_payment_id,
        refundAmount,
        booking.paymongo_fee,
        'Booking modification - reduced pax count',
      );

      if (refunded.error) {
        logger.error('Refund failed:', refunded.error);
      }

      await Transactions.create({
        booking_id: booking.id,
        host_id: booking.host_id,
        customer_id: userId,
        type: 'Refund',
        action: 'Booking Modify',
        amount: refunded?.attributes?.amount
          ? parseAmount(refunded.attributes.amount)
          : Number(refundAmount) - Number(booking.paymongo_fee),
        status: refunded.error ? 'Pending' : 'Paid',
        payment_intent_id: booking.payment_intent_id,
        method: null,
      });
    }

    booking.is_customer_modified = true;

    await booking.save();

    if (difference !== 0) {
      await Transactions.create({
        host_id: booking.host_id,
        booking_id: booking.id,
        customer_id: userId,
        type: difference > 0 ? 'Payment' : 'Refund',
        action: 'Booking Modify',
        amount: Math.abs(difference),
        status: 'Pending',
        payment_intent_id: paymentIntentId,
        booking_additional_payment_id: booking_additional_payment
          ? booking_additional_payment.id
          : null,
      });
    }

    res.json({
      success: true,
      message:
        'Booking modified successfully' + (difference < 0 ? ' and refund is on the way.' : ''),
      invalidDiscount: booking.discount_code ? invalidDiscount : undefined,
      data: {
        grandTotal: grand_total,
        oldTotalAmount: modifyData.old_total_amount,
        additionalPayment: difference > 0 ? difference : 0,
        paymentUrl,
        refundAmount: difference < 0 ? Math.abs(difference) : 0,
      },
    });
    try {
      await sendEmail(
        modifiedBookingEmail(booking.email, {
          user_name: booking.fullname,
          [paxDifference > 0 ? 'added' : 'removed']: [
            `Guest ${paxDifference > 0 ? '+' : '-'} ${Math.abs(paxDifference)}`,
          ],
        }),
      );
    } catch (error) {
      logger.error(
        'Send email error:',
        error?.data?.message || error.message || 'Unknown error',
        error,
      );
    }
  } catch (error) {
    next(error);
  }
};

exports.retryModifyPayment = async (req, res, next) => {
  try {
    const { userId } = req;
    const { bookingNo, returnUrl } = req.body;

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

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

    if (!booking.is_customer_modified) {
      return res
        .status(400)
        .json({ success: false, message: 'Booking has not been modified by customer.' });
    }

    if (booking.customer_modify_payment_status === 'Paid') {
      return res
        .status(400)
        .json({ success: false, message: 'Booking additional payment is already paid.' });
    }

    const additionalPayment = await BookingAdditionalPayments.findOne({
      where: { booking_id: booking.id, status: 'Unpaid' },
    });

    if (additionalPayment) {
      const paymongoStatus = await getPaymentStatus(additionalPayment.payment_intent_id);
      if (paymongoStatus?.attributes?.status === 'succeeded') {
        const payment = paymongoStatus?.attributes?.payments?.[0];
        await additionalPayment.update({
          status: 'Paid',
          paymongo_payment_id: payment?.id ?? null,
          paymongo_fee: payment?.attributes?.fee ? payment.attributes.fee / 100 : 0,
          payment_method: payment?.attributes?.source?.type ?? null,
          amount_paid: payment?.attributes?.amount ? payment.attributes.amount / 100 : 0,
        });

        await booking.update({
          payment_status: 'Fully Paid',
          customer_modify_payment_status: 'Paid',
        });

        await Transactions.update(
          {
            status: 'Paid',
            paymongo_fee: payment?.attributes?.fee ? payment.attributes.fee / 100 : 0,
          },
          {
            where: {
              booking_id: booking.id,
              type: 'Payment',
              action: 'Booking Modify',
              payment_intent_id: additionalPayment.payment_intent_id,
            },
          },
        );

        return res
          .status(200)
          .json({ success: true, message: 'Additional payment is already paid.' });
      }
    }

    const listing = await Listings.findOne({ where: { id: booking.listing_id } });
    const user = await Users.findByPk(userId, {
      attributes: [
        'id',
        'firstname',
        'lastname',
        'email_address',
        'image',
        'address_city',
        'address_country',
      ],
    });

    const paymentDataRaw = await checkoutSession({
      amount: booking.customer_modify_payment,
      currency: 'PHP',
      description: `AirCamp Booking Modification ${booking.booking_no} - ${listing.name}`,
      bookingNo: booking.booking_no,
      successUrl: generateReturnUrl({
        returnUrl,
        bookingNo,
        listingId: booking.listing_id,
        success: true,
        booking: booking,
        listing,
        listingSpot: booking.spot,
        guestFee: booking.guest_fee,
        discount: booking.discount_amount,
        subtotal: booking.subtotal,
        totalPayment: booking.total_amount,
        user,
      }),
      failUrl: generateReturnUrl({
        returnUrl,
        bookingNo,
        listingId: booking.listing_id,
        success: false,
        booking: booking,
        listing,
        listingSpot: booking.spot,
        guestFee: booking.guest_fee,
        discount: booking.discount_amount,
        subtotal: booking.subtotal,
        totalPayment: booking.total_amount,
        user,
      }),
    });

    if (paymentDataRaw.error) {
      logger.error(
        'Payment retry error:' + paymentDataRaw.error + JSON.stringify(paymentDataRaw, null, 2),
      );
      return res.status(500).json({
        success: false,
        message: 'An error occurred while processing the payment.',
      });
    }

    await BookingAdditionalPayments.update(
      {
        payment_intent_id: paymentDataRaw.payment_intent_id ?? null,
        status: 'Unpaid',
      },
      {
        where: {
          booking_id: booking.id,
          status: 'Unpaid',
        },
      },
    );

    await Transactions.create({
      host_id: booking.host_id,
      booking_id: booking.id,
      customer_id: userId,
      type: 'Payment',
      action: 'Booking Modify Retry',
      amount: booking.customer_modify_payment,
      status: 'Pending',
      payment_intent_id: paymentDataRaw.payment_intent_id ?? null,
    });

    // Set proper headers for payment response
    res.setHeader('Content-Type', 'application/json');
    res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');

    res.status(201).json({
      success: true,
      message: 'Additional payment link generated. Please complete payment.',
      data: {
        bookingId: booking.id,
        bookingNo: booking.booking_no,
        paymentUrl: paymentDataRaw.payment_url,
        amount: booking.customer_modify_payment,
      },
    });
  } catch (error) {
    next(error);
  }
};

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

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

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

    if (booking.customer_id !== userId) {
      return errorHandler('You are not authorized to update this booking', 403);
    }

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

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

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

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

    let discount = 0,
      invalidDiscount = false;

    if (booking.discount_code) {
      try {
        const handledDiscount = await handleDiscount(
          booking.discount_code,
          listing,
          sub_total,
          booking.pax,
          userId,
        );
        discount = handledDiscount.discount;
      } catch (error) {
        invalidDiscount = true;
      }
    }

    const { grand_total } = calculateIncomes(sub_total, initial_guest_fee, 0, discount);

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

    res.json({
      success: true,
      message: 'Booking modification is available',
      invalidDiscount: booking.discount_code ? invalidDiscount : undefined,
      data: {
        grandTotal: grand_total,
        oldTotalAmount: Number(booking.total_amount),
        additionalPayment,
        refundAmount:
          Number(grand_total) - Number(booking.total_amount) < 0
            ? Math.abs(Number(grand_total) - Number(booking.total_amount))
            : 0,
      },
    });
  } catch (error) {
    next(error);
  }
};

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

    const booking = await Bookings.findOne({
      where: { booking_no: bookingNo },
    });
    if (!booking) {
      errorHandler('Booking not found', 404);
    }

    booking.modify_request = status;
    await booking.save();

    if (booking.email_on_response) {
      const host = await Users.findByPk(booking.host_id);
      const hostEmail = host.email_address;

      try {
        await sendEmail(
          modificationResponseEmail(hostEmail, {
            booking_no: booking.booking_no,
            user_name: booking.fullname,
            status: booking.modify_request,
          }),
        );
      } catch (error) {
        logger.error(
          'Send email error:',
          error?.data?.message || error.message || 'Unknown error',
          error,
        );
      }
    }

    return res.status(200).json({
      success: true,
      message: `Booking modification request ${booking.modify_request} successfully`,
    });
  } catch (error) {
    next(error);
  }
};

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

    const booking = await Bookings.findOne({
      where: { booking_no: bookingNo },
      include: [
        {
          model: Listings,
          as: 'listing',
          attributes: ['id', 'name'],
        },
        { model: ListingSpots, as: 'spot', attributes: ['name'] },
      ],
    });

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

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

    if (!booking.host_modification_fee) {
      errorHandler('No host modification, or is pending, or transaction is already done.', 400);
    }

    if (booking.host_modify_payment_status === 'Paid') {
      return res.status(400).json({
        success: false,
        message: 'Host modification fee has already been paid',
      });
    }

    const additionalPayment = await BookingAdditionalPayments.findOne({
      where: { booking_id: booking.id, description: 'Host Modification Payment' },
    });

    if (additionalPayment) {
      const paymongoStatus = await getPaymentStatus(additionalPayment.payment_intent_id);
      if (paymongoStatus?.attributes?.status === 'succeeded') {
        const payment = paymongoStatus?.attributes?.payments?.[0];
        await additionalPayment.update({
          status: 'Paid',
          paymongo_payment_id: payment?.id ?? null,
          paymongo_fee: payment?.attributes?.fee ? payment.attributes.fee / 100 : 0,
          payment_method: payment?.attributes?.source?.type ?? null,
          amount_paid: payment?.attributes?.amount ? payment.attributes.amount / 100 : 0,
        });

        await booking.update({
          payment_status: 'Fully Paid',
          host_modify_payment_status: 'Paid',
        });

        await Transactions.update(
          {
            status: 'Paid',
            paymongo_fee: payment?.attributes?.fee ? payment.attributes.fee / 100 : 0,
          },
          {
            where: {
              booking_id: booking.id,
              action: 'Pay Host Modified Booking',
              payment_intent_id: additionalPayment.payment_intent_id,
            },
          },
        );

        return res.status(200).json({
          success: true,
          message: 'Host modification payment is already paid.',
          refresh: true,
        });
      }
    }

    const user = await Users.findByPk(booking.customer_id, {
      attributes: ['id', 'firstname', 'lastname', 'email_address', 'image'],
    });

    const paymentDataRaw = await checkoutSession({
      amount: booking.host_modification_fee,
      currency: 'PHP',
      description: `AirCamp Host Modification Fee ${booking.booking_no} - ${booking.listing.name}`,
      bookingNo: booking.booking_no,
      successUrl: generateReturnUrl({
        returnUrl,
        bookingNo,
        listingId: booking.listing_id,
        success: true,
        booking: booking,
        listing: booking.listing,
        listingSpot: booking.spot,
        guestFee: booking.guest_fee,
        discount: booking.discount_amount,
        subtotal: booking.subtotal,
        totalPayment: booking.total_amount,
        user,
      }),
      failUrl: generateReturnUrl({
        returnUrl,
        bookingNo,
        listingId: booking.listing_id,
        success: false,
        booking: booking,
        listing: booking.listing,
        listingSpot: booking.spot,
        guestFee: booking.guest_fee,
        discount: booking.discount_amount,
        subtotal: booking.subtotal,
        totalPayment: booking.total_amount,
        user,
      }),
    });

    if (paymentDataRaw.error) {
      return res.status(500).json({
        success: false,
        message: 'Failed to create payment session',
        error: paymentDataRaw.error,
      });
    }

    const booking_additional_payment = await BookingAdditionalPayments.create({
      description: 'Host Modification Payment',
      booking_id: booking.id,
      amount: booking.host_modification_fee,
      payment_intent_id: paymentDataRaw.payment_intent_id ?? null,
      status: 'Unpaid',
    });

    booking.payment_status = 'Partially Paid';
    booking.host_modify_payment_status = 'Unpaid';
    await booking.save();

    await Transactions.create({
      host_id: booking.host_id,
      booking_id: booking.id,
      customer_id: req.userId,
      type: 'Payment',
      action: 'Pay Host Modified Booking',
      amount: booking.host_modification_fee,
      status: 'Pending',
      payment_intent_id: paymentDataRaw.payment_intent_id ?? null,
      booking_additional_payment_id: booking_additional_payment.id,
    });

    // Set proper headers for payment response
    res.setHeader('Content-Type', 'application/json');
    res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');

    return res.status(201).json({
      success: true,
      message: 'Payment session created for host modification fee. Please complete payment.',
      data: {
        bookingId: booking.id,
        bookingNo: booking.booking_no,
        paymentUrl: paymentDataRaw.payment_url,
        amount: booking.host_modification_fee,
        additionalPaymentId: booking_additional_payment.id,
      },
    });
  } catch (error) {
    next(error);
  }
};

exports.hasPreviousId = async (req, res, next) => {
  try {
    const { userId } = req;

    const validId = await BookingValidIds.findOne({
      where: {
        customer_id: userId,
      },
    });

    if (!validId) {
      return res.status(200).json({
        success: true,
        message: 'No Previous Booking with Government ID',
        data: false,
      });
    }

    return res.status(200).json({
      success: true,
      message: 'Previous Booking with Government ID found',
      data: true,
    });
  } catch (error) {
    next(error);
  }
};

const handleDiscount = async (discountCode, listing, subTotal, pax, customer_id) => {
  let discount, discountData;
  let listingDiscount = await ListingDiscounts.findOne({
    where: {
      code: discountCode,
      listing_id: listing.id,
      status: 'active',
    },
  });

  if (listingDiscount) {
    let valid = true;
    if (listingDiscount.start_date && listingDiscount.end_date) {
      valid =
        new Date(listingDiscount.start_date) <= new Date() &&
        new Date(listingDiscount.end_date) >= new Date();
    }
    if (!valid) {
      errorHandler('Discount is not valid for the current date', 409);
    }

    discount = subTotal * Number(listingDiscount.percentage);

    discountData = {
      discount_percentage: Number(listingDiscount.percentage),
      discount_amount: discount,
      discount_code: listingDiscount.code,
      discount_type: 'By Percentage',
    };

    if (customer_id) {
      const existingUserDiscount = await UserDiscounts.findOne({
        where: {
          customer_id,
          listing_discount_id: listingDiscount.id,
          status: 'active',
        },
      });

      if (existingUserDiscount) {
        existingUserDiscount.status = 'used';
        existingUserDiscount.save();
      }
    }
  } else {
    const globalDiscount = await Discounts.findOne({
      where: { code: discountCode },
    });

    if (!globalDiscount) {
      errorHandler('Discount is not valid', 409);
    }

    let validDiscount = true;
    if (globalDiscount.validity_start_date && globalDiscount.validity_end_date) {
      validDiscount =
        new Date(globalDiscount.validity_start_date) <= new Date() &&
        new Date(globalDiscount.validity_end_date) >= new Date();
    }
    if (!validDiscount) {
      errorHandler('Discount is not valid for the current date', 409);
    }

    const type = globalDiscount.type;

    if (
      type === 'Amount To Reach' &&
      globalDiscount.amount &&
      subTotal < Number(globalDiscount.amount)
    ) {
      errorHandler(
        'Discount is not valid for the amount, ' + globalDiscount.amount + '+ amount required.',
        409,
      );
    }

    if (
      type === 'Capacity To Reach' &&
      globalDiscount.num_of_pax &&
      pax < Number(globalDiscount.num_of_pax)
    ) {
      errorHandler(
        'Discount is not valid for the number of pax, ' +
          globalDiscount.num_of_pax +
          '+ pax required.',
        409,
      );
    }
    discount = subTotal * Number(globalDiscount.percentage);
    discountData = {
      discount_percentage: Number(globalDiscount.percentage),
      discount_type: globalDiscount.type,
      discount_num_of_pax: Number(globalDiscount.num_of_pax),
      discount_code: globalDiscount.code,
      discount_amount: discount,
      discount_amount_to_reach: Number(globalDiscount.amount),
    };

    if (customer_id) {
      const existingUserDiscount = await UserDiscounts.findOne({
        where: {
          customer_id,
          discount_id: globalDiscount.id,
          status: 'active',
        },
      });

      if (existingUserDiscount) {
        existingUserDiscount.status = 'used';
        existingUserDiscount.save();
      }
    }
  }

  return { discount, discountData };
};

exports.handleDiscount = handleDiscount;
exports.syncPaymentStatus = async (req, res, next) => {
  try {
    const { bookingNo } = req.params;

    const result = await syncPayment(bookingNo);

    res.status(200).json(result);
  } catch (error) {
    console.error('Sync payment status error:', error);
    res.status(500).json({
      success: false,
      message: error.message || 'Failed to sync payment status',
    });
  }
};

// exports.getBookingPaymentDebug = async (req, res, next) => {
// 	try {
// 		const { bookingNo } = req.params;
// 		const { userId } = req;

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

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

// 		let paymongoData = null;
// 		if (booking.payment_intent_id) {
// 			try {
// 				paymongoData = await getSource(booking.payment_intent_id);
// 			} catch (error) {
// 				logger.error('Error fetching PayMongo source:', error);
// 			}
// 		}

// 		res.status(200).json({
// 			success: true,
// 			data: {
// 				booking: {
// 					id: booking.id,
// 					booking_no: booking.booking_no,
// 					status: booking.status,
// 					payment_status: booking.payment_status,
// 					total_amount: booking.total_amount,
// 					amount_paid: booking.amount_paid,
// 					balance: booking.balance,
// 					createdAt: booking.createdAt,
// 					updatedAt: booking.updatedAt,
// 				},
// 				transactions: booking.transactions,
// 				paymongo_data: paymongoData,
// 				environment: {
// 					using_test_keys: process.env.PAYMONGO_SECRET_KEY?.startsWith('sk_test_'),
// 					webhook_configured: !!process.env.PAYMONGO_WEBHOOK_SECRET,
// 				},
// 			},
// 		});
// 	} catch (error) {
// 		next(error);
// 	}
// };

// const getSource = async (sourceId) => {
// 	try {
// 		const response = await axios.get(`${process.env.PAYMONGO_API_BASE_URL}/sources/${sourceId}`, {
// 			headers: {
// 				Authorization: `Basic ${Buffer.from(process.env.PAYMONGO_SECRET_KEY + ':').toString('base64')}`,
// 				'Content-Type': 'application/json',
// 			},
// 		});

// 		return {
// 			id: response.data.data.id,
// 			status: response.data.data.attributes.status,
// 			amount: parseAmount(response.data.data.attributes.amount),
// 			currency: response.data.data.attributes.currency,
// 			type: response.data.data.attributes.type,
// 			redirect: response.data.data.attributes.redirect,
// 			billing: response.data.data.attributes.billing,
// 			metadata: response.data.data.attributes.metadata,
// 			created_at: response.data.data.attributes.created_at,
// 			updated_at: response.data.data.attributes.updated_at,
// 		};
// 	} catch (error) {
// 		logger.error('PayMongo getSource error:', error.response?.data || error.message, error);
// 		throw new Error(error.response?.data?.errors?.[0]?.detail || 'Failed to retrieve source');
// 	}
// };
