"use strict";
const ezeeHelper = require("../helpers/ezeeHelperImproved");
const RoomService = require("../services/roomService");
const roomService = new RoomService();
const property = require("../models/propertyModel");
const Property = new property();
const bookingHelper = require("../helpers/bookingHelper");
const BookingHelper = new bookingHelper();
const booking = require("../models/bookingModel");
const Booking = new booking();
const user = require("../models/userModel");
const User = new user();
const smModel = require("../models/smModel");
const SMModel = new smModel();
const settingModel = require("../models/settingModel");
const Setting = new settingModel();
const razorPayHelper = require("../helpers/razorPayHelper");
const RazorPayHelper = new razorPayHelper();
const fieldNames = require("../config/tableFieldNames");
const CREATE_BOOKING = "createBooking";
const PROCESS_POST_BOOKING = "processPostBooking";
const fields = require("../config/configs");
const {
  ezee_bookByInfo_fields,
  ezee_BookingTran_fields,
  ezee_RentalInfo_fields,
  ezee_TaxDetails_fields,
  ezee_PaymentDetails_fields,
  booking_tables,
} = fields;
const utility = require("../helpers/utility");
const Utility = new utility();
const bookingTableMappings = require("../config/ezeeTableConfig");
const { bookingTables, transactionTables } = bookingTableMappings;
const Response = require("../helpers/responseHelper");

const {
  format,
  addDays,
  differenceInDays,
  eachDayOfInterval,
} = require("date-fns");

const xml2js = require("xml2js");
const { json } = require("body-parser");
const S3Helper = require("../helpers/s3Helper");
const settingHelper = require("../helpers/settingsHelper");
const SettingsHelper = new settingHelper();
const s3Helper = new S3Helper();

class BookingsController {
  constructor(ezeeHelper) {
    this.ezeeHelper = ezeeHelper;
  }

  // Map user reason to Ezee-compatible reason codes
  mapToEzeeReasonCode(userReason, blockType) {
    // Common Ezee reason codes that are typically accepted
    const ezeeReasonCodes = {
      maintenance: "Maintenance",
      repair: "Maintenance",
      cleaning: "Maintenance",
      renovation: "Maintenance",
      owner: "Owner Block",
      personal: "Owner Block",
      family: "Owner Block",
      testing: "Maintenance",
      test: "Maintenance",
      demo: "Maintenance",
      inspection: "Maintenance",
      upgrade: "Maintenance",
      fix: "Maintenance",
      broken: "Maintenance",
      damage: "Maintenance",
      "out of order": "Out of Order",
      unavailable: "Out of Order",
      closed: "Out of Order",
      emergency: "Emergency",
      urgent: "Emergency",
    };

    // Clean and normalize the user reason
    const cleanReason = userReason ? userReason.toLowerCase().trim() : "";

    // Check if user reason matches any known patterns
    for (const [pattern, ezeeCode] of Object.entries(ezeeReasonCodes)) {
      if (cleanReason.includes(pattern)) {
        return ezeeCode;
      }
    }

    // If no match found, use blockType-based mapping
    if (blockType === "Owner block") {
      return "Owner Block";
    } else if (
      blockType === "Maintainance Block" ||
      blockType === "Maintenance Block"
    ) {
      return "Maintenance";
    } else {
      // Default to Maintenance for any other cases
      return "Maintenance";
    }
  }

  // Alternative method to try different Ezee reason codes if the first one fails
  getAlternativeEzeeReasonCode(originalReason, blockType) {
    111111;
    // Try different variations that might work with Ezee
    const alternatives = [
      "Maintenance",
      "Out of Order",
      "OOO",
      "Block",
      "Unavailable",
      "Closed",
    ];

    // Return the first alternative (Maintenance is most commonly accepted)
    return alternatives[0];
  }

  async index(req, res) {
    await res.render("bookings/list.ejs");
  }

  async edit(req, res) {
    const { id } = req.params;
    res.render("bookings/edit.ejs", {});
    //res.render('bookingEdit.ejs', { results:settings,property:property[0] });
  }

  async channelIds(req, res) {
    const results = await this.ezeeHelper.getPropertyTypes();
    res.render("channelIds.ejs", { results });
  }

  async importEzeeSyncLogs(req, res) {
    const syncLogs = await Booking.getAllSyncLogs();
    syncLogs.forEach(async (sl) => {
      var log = JSON.parse(sl.log);
      var data = log.data.Reservations;
      for (let i = 0; i < data.Reservation.length; i++) {
        const reservation = data.Reservation[i];
        const uniqueId = reservation.UniqueID;
        const retrievedReservation =
          await this.ezeeHelper.retrieveSingleBooking(reservation.UniqueID);
        try {
          if (uniqueId && uniqueId != "") {
            await Booking.clearEzeeBooking(uniqueId);
          }
        } catch (error) {
          console.log("error while deleting");
          console.log(error);
        }
        await this.retrieveAndImport(retrievedReservation, uniqueId);
      }
      await Booking.markAsImported(sl.id);
    });
    return Response.success(res, "Sync logs imported successfully");
  }

  async missedOutBookings(req, res) {
    console.log("------------- start of missedOutBookings");
    const missed = await Booking.missing5();
    var data = [];
    for (let i = 0; i < missed.length; i++) {
      const missedBooking = missed[i];
      console.log("Starting: " + missedBooking.UniqueID);
      data.push(missedBooking.UniqueID);
      const retrievedReservation = await this.ezeeHelper.retrieveSingleBooking(
        missedBooking.UniqueID,
      );
      await this.retrieveAndImport(
        retrievedReservation,
        missedBooking.UniqueID,
      );
      await Booking.markMissedAsImported(missedBooking.UniqueID);
      console.log("Finished: " + missedBooking.UniqueID);
    }
    console.log("------------- end of missedOutBookings");
    return Response.success(res, { data: data });
  }

  async generateOrderId(req, res) {
    const { amount, currency } = req.body;
    if (!amount || !currency) {
      return Response.error(
        res,
        "VALIDATION_ERROR",
        "Mandatory booking parameters missing or incorrect!",
        400,
      );
    }
    try {
      const data = await RazorPayHelper.getOrderId(amount, currency);
      return Response.success(res, data);
    } catch (error) {
      return Response.error(
        res,
        "INTERNAL_ERROR",
        "Could not generate orderId!",
        500,
      );
    }
  }

  async retrieveAndImport(retrievedReservation, uniqueId) {
    var extraFields = { UniqueId: uniqueId };
    const reservations = retrievedReservation.Reservations;
    if (reservations) {
      for (let i = 0; i < reservations.Reservation.length; i++) {
        const reservation = reservations.Reservation[i];
        console.log(`***** Importing booking with UniqueId: ${uniqueId} *****`);
        //console.log("insert into temp_BookByInfo");
        //await Booking.writeEzeeBookingTable('temp_BookByInfo',await this.populateFields(ezee_bookByInfo_fields,reservation));
        const bookingTrans = reservation.BookingTran;
        for (let j = 0; j < bookingTrans.length; j++) {
          const bookingTran = bookingTrans[j];
          /*Save bookingTran*/
          //console.log("insert into temp_BookingTran");
          /*await Booking.writeEzeeBookingTable('temp_BookingTran',await this.populateFields(ezee_BookingTran_fields,bookingTran,extraFields))
                    
                    const taxDetails = bookingTran.TaxDeatil;
                    
                    if(taxDetails){
                        for(let k=0;k<taxDetails.length; k++){
                            const taxDetail = taxDetails[k];
                            /*Save taxDetail*
                            //console.log("insert into temp_taxDetails");
                            await Booking.writeEzeeBookingTable('temp_taxDetails',await this.populateFields(ezee_TaxDetails_fields,taxDetail,{UniqueId:uniqueId,SubBookingId:bookingTran.SubBookingId}));
                        }
                    }
                    const paymentDetails = bookingTran.PaymentDetail;
                    if(paymentDetails){
                        for(let l=0;l<paymentDetails.length; l++){
                            const paymentDetail = paymentDetails[l];
                            /*Save paymentDetail*
                            //console.log("insert into temp_paymentDetails");
                            await Booking.writeEzeeBookingTable('temp_paymentDetails',await this.populateFields(ezee_PaymentDetails_fields,paymentDetail,{UniqueId:uniqueId,SubBookingId:bookingTran.SubBookingId}));
                        }
                    }*/
          const rentalInfos = bookingTran.RentalInfo;

          if (rentalInfos) {
            for (let m = 0; m < rentalInfos.length; m++) {
              const rentalInfo = rentalInfos[m];
              /*Save rentalInfo*/
              //console.log("insert into temp_RentalInfo");
              await Booking.writeEzeeBookingTable(
                "temp_RentalInfo",
                await this.populateFields(ezee_RentalInfo_fields, rentalInfo, {
                  UniqueId: uniqueId,
                  SubBookingId: bookingTran.SubBookingId,
                }),
              );
            }
          }
        }
      }
    } else {
      console.log("no reservation found");
    }
    return;
  }

  /* This function should move to some kind of util */
  async populateFields(fieldsList, fieldSet, extraFields = null) {
    const values = {};
    fieldsList.forEach((fieldName) => {
      if (Object.hasOwnProperty.bind(fieldSet)(fieldName)) {
        values[fieldName] = fieldSet[fieldName];
      }
    });
    if (extraFields) {
      for (var fieldName in extraFields) {
        values[fieldName] = extraFields[fieldName];
      }
    }
    return values;
  }

  /** called by the ezee booking webhook*/
  async ezeeBookingSync(req, res) {
    console.log("Booking Webhook called by ezee request");
    var logId = 0;
    try {
      logId = await Booking.logEzeeBookingInput(req.body);
    } catch (error) {
      console.log("error while logging ezee booking");
      console.log(error);
    }

    try {
      const reservation = req.body.data.Reservations.Reservation[0];
      const bookingTran = reservation.BookingTran[0];
      const uniqueId = reservation.UniqueID;
      if (uniqueId && uniqueId != "") {
        await Booking.clearEzeeBooking(uniqueId);
        console.log("deleted existing booking");
      }
      console.log("Beginning quick import");
      const rentalInfos = bookingTran.RentalInfo;
      if (rentalInfos) {
        for (let m = 0; m < rentalInfos.length; m++) {
          const rentalInfo = rentalInfos[m];
          await Booking.writeEzeeBookingTable(
            "temp_RentalInfo",
            await this.populateFields(ezee_RentalInfo_fields, rentalInfo, {
              UniqueId: uniqueId,
              SubBookingId: bookingTran.SubBookingId,
            }),
          );
        }
      }
      //await this.retrieveAndImport(retrievedReservation,uniqueId);
      console.log("Finished quick import");
      console.log("Beginning detailed import");
      var success = await this.importBooking(logId);
      if (success) {
        await Booking.markImported(logId);
        console.log("Marked as imported");
      }
      console.log("Finished detailed import");
    } catch (error) {
      console.log("error while importing ezee booking");
      console.log(error);
    }
    return Response.success(res, "Ezee booking sync completed");

    /*console.log("Beginning quick import");
        const data = req.body.data.Reservations;
        for(let i=0; i<data.Reservation.length; i++){
            const reservation = data.Reservation[i];
            const uniqueId = reservation.UniqueID;
            const retrievedReservation = await this.ezeeHelper.retrieveSingleBooking(reservation.UniqueID);
            try {
                if(uniqueId && uniqueId != ''){
                    await Booking.clearEzeeBooking(uniqueId);
                    console.log("deleted existing booking");
                }
            } catch (error) {
                console.log("error while deleting");
                console.log(error);
            }
            await this.retrieveAndImport(retrievedReservation,uniqueId);
            console.log("Finished quick import");
            try {
                console.log("Beginning detailed import");
                var success = await this.importBooking(logId);
                if(success){
                    await Booking.markImported(logId);
                    console.log("Marked as imported");
                }
                console.log("Finished detailed import");
            } catch (error) {
                console.log("error while deleting");
                console.log(error);
            }
        }
        res.status(200).json({});*/
  }

  async historicalCrossCheck(req, res) {
    const from = await Booking.doneUpto();
    const to = format(addDays(from, 2), "yyyy-MM-dd");
    var data = await this.ezeeHelper.historicalBookings(from, to);
    var r2 = await xml2js.parseString(data);
    var reservations;
    var i = 0;
    await xml2js.parseString(data, async (err, result) => {
      if (err) {
        console.error(err);
        return;
      }
      var bookByInfo;
      var bookingTrans;
      try {
        var errors = result.RES_Response.Errors[0];
        console.log("Error code: " + errors.ErrorCode);
        if (errors && errors.ErrorCode != 0) {
          console.log("Found non zero error code");
          return { status: 400, data: errors.ErrorMessage };
        }
        reservations = result.RES_Response.Reservations[0].Reservation;
        if (!reservations || reservations.length == 0) {
          console.log("No reservations found");
          await Booking.updateUpto(format(addDays(from, 1), "yyyy-MM-dd"));
          return { status: 400, data: data };
        }

        for (i = 0; i < reservations.length; i++) {
          bookByInfo = reservations[i].BookByInfo[0];
          await Booking.insertTempCheck(bookByInfo["UniqueID"]);
        }
        await Booking.updateUpto(format(addDays(from, 1), "yyyy-MM-dd"));
        await Booking.manageTempCheck();
      } catch (error) {
        console.log("Some error happened. Probably no records found");
        console.log(error);
        return { status: 400, data: error };
      }
    });

return Response.success(res, {
  total: i,
  from,
  to
});
  }

  async historicalBookings(req, res) {
    var data = await this.ezeeHelper.historicalBookings(
      req.body.start,
      req.body.end,
    );
    var r2 = await xml2js.parseString(data);
    var reservations;
    await xml2js.parseString(data, async (err, result) => {
      if (err) {
        console.error(err);
        return;
      }
      var bookByInfo;
      var bookingTrans;
      var errors = result.RES_Response.Errors[0];
      console.log("Error code: " + errors.ErrorCode);
      if (errors && errors.ErrorCode != 0) {
        console.log("Found non zero error code");
        return { status: 400, data: errors.ErrorMessage };
      }
      reservations = result.RES_Response.Reservations[0].Reservation;
      if (!reservations || reservations.length == 0) {
        console.log("No reservations found");
        return { status: 400, data: data };
      }
      var i;
      for (i = 0; i < reservations.length; i++) {
        bookByInfo = reservations[i].BookByInfo[0];
        const uniqueId = bookByInfo["UniqueID"];
        try {
          if (uniqueId && uniqueId != "") {
            await Booking.clearEzeeBooking(uniqueId);
            console.log("deleted existing booking");
          }
        } catch (error) {
          console.log("error while deleting");
          console.log(error);
        }

        const bookByInfoValues = {};
        ezee_bookByInfo_fields.forEach((fieldName) => {
          if (Object.hasOwnProperty.bind(bookByInfo)(fieldName)) {
            bookByInfoValues[fieldName] = bookByInfo[fieldName];
          }
        });
        var temp_booking_id = await Booking.writeEzeeBookingTable(
          "temp_BookByInfo",
          bookByInfoValues,
        );

        bookingTrans = bookByInfo.BookingTran;
        for (let bt = 0; bt < bookingTrans.length; bt++) {
          const bookingTran = bookingTrans[bt];
          const SubBookingId = bookingTran.SubBookingId;
          const bookingTranValues = {};
          ezee_BookingTran_fields.forEach((fieldName) => {
            if (Object.hasOwnProperty.bind(bookingTran)(fieldName)) {
              bookingTranValues[fieldName] = bookingTran[fieldName];
            }
          });
          bookingTranValues["temp_booking_id"] = temp_booking_id;
          bookingTranValues["uniqueId"] = uniqueId;
          var temp_tran_id = await Booking.writeEzeeBookingTable(
            "temp_BookingTran",
            bookingTranValues,
          );

          const rentalInfos = bookingTran.RentalInfo;
          for (let j = 0; j < rentalInfos.length; j++) {
            const rentalInfo = rentalInfos[j];
            const rentalInfoValues = {};
            rentalInfoValues["temp_booking_id"] = temp_tran_id;
            rentalInfoValues["uniqueId"] = uniqueId;
            rentalInfoValues["SubBookingId"] = SubBookingId;
            ezee_RentalInfo_fields.forEach((fieldName) => {
              if (Object.hasOwnProperty.bind(rentalInfo)(fieldName)) {
                rentalInfoValues[fieldName] = rentalInfo[fieldName];
              }
            });
            await Booking.writeEzeeBookingTable(
              "temp_RentalInfo",
              rentalInfoValues,
            );
          }
        }
      }
      console.log("Total Reservations: " + i);
    });
return Response.success(res, data);
  }

  async bookingOnEzee(params, property, lead_guest) {
    var bookingDataString = await this.createBookingDataString(
      params,
      property,
      lead_guest,
    );
    console.log(bookingDataString);
    //make booking on ezee
    var results = await this.ezeeHelper.createBooking({
      BookingData: bookingDataString,
    });
    if (results.status != 200) {
      return { status: 400, data: results.output };
    }
    var ezeeBooking = results.data;
    if (ezeeBooking.hasOwnProperty("ErrorCode")) {
      return { status: 400, data: results.output };
    }

    results = await this.ezeeHelper.processPostBooking({
      Process_Data:
        '{"Action":"ConfirmBooking","ReservationNo": "' +
        ezeeBooking.ReservationNo +
        '","Inventory_Mode":"' +
        ezeeBooking.Inventory_Mode +
        '","Error_Text":""}',
    });
    if (results.status != 200) {
      return { status: 400, data: results.output };
    }
    var postBooking = results.data;
    if (
      !postBooking.hasOwnProperty("result") ||
      postBooking.result != "success"
    ) {
      return { status: 400, data: results.output };
    }
    return { status: 200, ReservationNo: ezeeBooking.ReservationNo };
  }

  async create(req, res) {
    try {
      /* check if webUser is logged in */
      var loggedInUser = req.guest;
      if (!loggedInUser) {
        return Response.error(
          res,
          "UNAUTHORIZED",
          "User not authenticated!",
          401,
        );
      }
      console.log(req.body);
      const guestRecord = await User.getById(req.guest.id);
      //* Validate booking parameters */
      const {
        check_in_date,
        check_out_date,
        property_id,
        number_adults,
        number_children,
        main_guest,
        breakfast_included,
        bookingForSelf,
        transaction_id,
      } = req.body;
      if (
        !check_in_date ||
        !check_out_date ||
        !property_id ||
        !number_adults ||
        !number_children
      ) {
        return Response.error(
          res,
          "VALIDATION_ERROR",
          "Mandatory booking parameters missing or incorrect!",
          400,
        );
      }
      //* Validate payment transaction id */
      if (!transaction_id || transaction_id == null || transaction_id == "") {
        return Response.error(
          res,
          "VALIDATION_ERROR",
          "Payment information missing or incorrect!",
          400,
        );
      }
      /* Validate main guest details */
      if (!main_guest) {
        return Response.error(
          res,
          "VALIDATION_ERROR",
          "Incomplete or incorrect guest information!!",
          400,
        );
      }
      const { firstname, lastname, email, phone } = JSON.parse(main_guest);
      if (!firstname || !lastname || !email || !phone) {
        return Response.error(
          res,
          "VALIDATION_ERROR",
          "Incomplete or incorrect guest information!!",
          400,
        );
      }
      /* Validate property exists */
      var property = await Property.getById(req.body.property_id);
      if (!property || property.length == 0) {
        return Response.error(res, "NOT_FOUND", "Property not found", 404);
      }
      var breakfst_available = property.meals_available;
      if (breakfast_included && breakfst_available == 0) {
        res;
        return Response.error(
          res,
          "VALIDATION_ERROR",
          "No breakfast available",
          400,
        );
      }

      //crete params object
      const params = {
        check_in_date: req.body.check_in_date,
        check_out_date: req.body.check_out_date,
        number_adults: req.body.number_adults,
        number_children: req.body.number_children,
        roomtypeunkid: property[0].channel_id,
      };
      //check availability
      const prop = await this.ezeeHelper.isPropertyAvailable(params);
      if (!prop.status) {
        /** return error from here */
        return Response.error(
          res,
          "PROPERTY_UNAVAILABLE",
          "Property unavailable for the selected criteria",
          400,
        );
      }

      var lead_guest = JSON.parse(main_guest);
      lead_guest.phone = guestRecord[0].phone;

      /* Make reservation on Ezee */
      var reservation = await this.bookingOnEzee(
        params,
        prop.property,
        lead_guest,
      );
      if (reservation.status != 200) {
        return Response.error(
          res,
          "BOOKING_FAILED",
          reservation.data || "Failed to create booking",
          400,
        );

        return;
      }
      var uniqueId = reservation.ReservationNo;
      var temp_booking_values = {};
      temp_booking_values["uniqueId"] = uniqueId;
      temp_booking_values["guest_id"] = loggedInUser.id;
      temp_booking_values["property_id"] = req.body.property_id;
      temp_booking_values["check_in_date"] = req.body.check_in_date;
      temp_booking_values["check_out_date"] = req.body.check_out_date;
      temp_booking_values["number_adults"] = req.body.number_adults;
      temp_booking_values["number_children"] = req.body.number_children;
      await Booking.writeTableEntry("booking_temp", temp_booking_values);

      lead_guest["lead_guest"] = 1;
      lead_guest["user_id"] = loggedInUser.id;
      lead_guest["role"] = 268;
      lead_guest["booking_id"] = uniqueId;
      await this.bookingGuests(lead_guest);
      if (req.body.guests) {
        var guests = JSON.parse(req.body.guests);
        guests.forEach(async (guest) => {
          guest["booking_id"] = uniqueId;
          guest["lead_guest"] = 0;
          guest["role"] = 268;
          await this.bookingGuests(guest);
        });
      }
      //save payment details to db
      var info = await BookingHelper.getBookingDisplayInfo(uniqueId);
      await BookingHelper.sendConfirmationEmail(
        temp_booking_values,
        await this.amountPaidByCustomer(
          prop.property,
          req.body.number_adults,
          req.body.number_children,
        ),
        lead_guest["email"],
      );
      return Response.success(res, { info });
    } catch (error) {
      return Response.error(res, "INTERNAL_ERROR", error.message, 500);
    }
  }

  async pricingCalcs(ezeeProp, number_adults, number_children, property) {
    var extraAdults = number_adults - parseInt(ezeeProp.base_adult_occupancy);
    var extraChildren =
      number_children - parseInt(ezeeProp.base_child_occupancy);
    var totalprice_room_only = ezeeProp.room_rates_info.totalprice_room_only;
    var totalprice_inclusive_all =
      ezeeProp.room_rates_info.totalprice_inclusive_all;
    if (extraAdults > 0) {
      Object.values(ezeeProp.extra_adult_rates_info.exclusive_tax).forEach(
        (value) => {
          totalprice_room_only += extraAdults * value;
        },
      );
      Object.values(
        ezeeProp.extra_adult_rates_info.inclusive_tax_adjustment,
      ).forEach((value) => {
        totalprice_inclusive_all += extraAdults * value;
      });
    }
    if (extraChildren > 0) {
      Object.values(ezeeProp.extra_child_rates_info.exclusive_tax).forEach(
        (value) => {
          totalprice_room_only += extraChildren * value;
        },
      );
      Object.values(
        ezeeProp.extra_child_rates_info.inclusive_tax_adjustment,
      ).forEach((value) => {
        totalprice_inclusive_all += extraChildren * value;
      });
    }
    var total_taxes = totalprice_inclusive_all - totalprice_room_only;
    var prices = {};
    prices["totalprice_room_only"] = totalprice_room_only;
    prices["total_taxes"] = total_taxes;
    prices["totalprice_inclusive_all"] = totalprice_inclusive_all;
    return prices;
  }

  async bookingGuests(guest) {
    try {
      var fieldValues = {};
      fieldNames["booking_guests_fields"].forEach((fieldName) => {
        if (Object.hasOwnProperty.bind(guest)(fieldName)) {
          fieldValues[fieldName] = guest[fieldName];
        }
      });

      var userValues = {};
      fieldNames["user_fields"].forEach((fieldName) => {
        if (Object.hasOwnProperty.bind(guest)(fieldName)) {
          userValues[fieldName] = guest[fieldName];
        }
      });

      if (!fieldValues["user_id"]) {
        var user = await User.store(userValues);
        fieldValues["user_id"] = user.id;
      } else {
        var user = await User.updateUser(userValues, fieldValues["user_id"]);
      }
      var guestValues = {
        uniqueId: fieldValues["booking_id"],
        guest_id: fieldValues["user_id"],
        lead_guest: fieldValues["lead_guest"],
      };
      await Booking.saveBookingGuests(guestValues);
    } catch (error) {
      console.error(error);
     return Response.error(res, "INTERNAL_ERROR", error.message, 500);
    }
  }

  async bookingPayments(booking_id, transaction_id, amount) {}

  async createBookingDataString(params, property, guest) {
    try {
      const { firstname, lastname, email, phone } = guest;
      var webDiscount = await SettingsHelper.webDiscount();
      var baseRates = await this.commaSeparatedValues(
        property.room_rates_info.exclusivetax_baserate,
        webDiscount,
      );
      var extraAdultRates = await this.commaSeparatedValues(
        property.extra_adult_rates_info.exclusive_tax,
      );
      var extraChildRates = await this.commaSeparatedValues(
        property.extra_child_rates_info.exclusive_tax,
      );
      return (
        '{"Room_Details":{"Room_1":{"Rateplan_Id":"' +
        property.roomrateunkid +
        '","Ratetype_Id":"' +
        property.ratetypeunkid +
        '","Roomtype_Id":"' +
        property.roomtypeunkid +
        '","baserate":"' +
        baseRates +
        '","extradultrate":"' +
        extraAdultRates +
        '","extrachildrate":"' +
        extraChildRates +
        '","number_adults":"' +
        params.number_adults +
        '","number_children":"' +
        params.number_children +
        '","ExtraChild_Age":"0","Title":"","First_Name":"' +
        firstname +
        '","Last_Name":"' +
        lastname +
        '","Gender":"","SpecialRequest":""}},"check_in_date":"' +
        params.check_in_date +
        '","check_out_date":"' +
        params.check_out_date +
        '","Booking_Payment_Mode":"3","Email_Address":"' +
        email +
        '","Source_Id":"","MobileNo":"' +
        phone +
        '","Address":"","State":"","Country":"","City":"","Zipcode":"","Fax":"","Device":"","Languagekey":"en","paymenttypeunkid":"4058300000000000182"}'
      );
    } catch (error) {
      console.log("error in createBookingDataString");
    }
  }

  async commaSeparatedValues(prop, discountPercentage = 0) {
    var arrObject = Object.keys(prop).map(function (key) {
      if (discountPercentage && discountPercentage > 0) {
        return prop[key] - (prop[key] * discountPercentage) / 100;
      } else {
        return prop[key];
      }
    });
    return arrObject.map((item) => item).join(",");
  }

  async amountPaidByCustomer(property, adults, children) {
    var webDiscount = await SettingsHelper.webDiscount();
    var totalPrice =
      (property.room_rates_info.totalprice_room_only * (100 - webDiscount)) /
      100;

    var extraAdults = adults - parseInt(property.base_adult_occupancy);
    var extraChildren = children - parseInt(property.base_child_occupancy);
    if (extraAdults > 0) {
      Object.values(property.extra_adult_rates_info.exclusive_tax).forEach(
        (value) => {
          totalPrice += extraAdults * value;
        },
      );
    }
    if (extraChildren > 0) {
      Object.values(property.extra_child_rates_info.exclusive_tax).forEach(
        (value) => {
          totalPrice += extraChildren * value;
        },
      );
    }
    totalPrice += totalPrice * 0.18;
    return totalPrice;
  }

  async displayInfo(req, res) {
    const { id } = req.params;
    var info = await BookingHelper.getBookingDisplayInfo(id);
    console.log(info);
    return Response.success(res, { info });
  }

  async myTrips(req, res) {
    console.log("myTrips");
    try {
      const userId = req.guest.id;
      //const userId = 2179;
      const { trips, occupancy } = await Booking.myTrips(userId);
      //console.log(trips);
      //console.log(trips.map(trip => {return {bucket:`${process.env.AWS_PROPERTY_BUCKET}/${trip.property_id}`, key:trip.media_filename}}));
      const s3Urls = await s3Helper.getFilesFromS3(
        trips.map((trip) => {
          return {
            bucket: `${process.env.AWS_PROPERTY_BUCKET}/${trip.property_id}`,
            key: trip.media_filename,
          };
        }),
      );
      //console.log(s3Urls);
      trips.forEach((trip) => {
        const s3Url = s3Urls.find(
          (s3Url) => s3Url.fileName == trip.media_filename,
        );
        trip["s3Url"] = s3Url ? s3Url.url : "";
        trip["guests"] = 0;
        const occ = occupancy.find((o) => o.booking_id == trip.id);
        if (occ) {
          trip["guests"] = occ.adults + occ.children;
        }
      });
      const cancelled = trips.filter(
        (trip) => trip.status == "Void" || trip.status == "Cancel",
      );
      const nonCancelled = trips.filter(
        (trip) => trip.status != "Void" && trip.status != "Cancel",
      );
      const today = format(new Date(), "yyyy-MM-dd");
      const upcoming = nonCancelled.filter(
        (trip) => differenceInDays(trip.start, today) > 0,
      );
      const past = nonCancelled.filter(
        (trip) => differenceInDays(trip.end, today) <= 0,
      );
      const ongoing = nonCancelled.filter(
        (trip) =>
          differenceInDays(trip.start, today) <= 0 &&
          differenceInDays(trip.end, today) >= 0,
      );
      return Response.success(res, {
        cancelled,
        upcoming,
        past,
        ongoing,
      });
    } catch (error) {
      console.log("error in myTrips");
      console.log(error);
      return Response.error(
        res,
        "INTERNAL_ERROR",
        "Error while fetching my trips",
        500,
      );
    }
  }

  async concierge(req, res) {
    try {
      const parent = req.body.parent || 0;
      const services = await Booking.select("concierge_services", {
        parent: parent,
        status: 1,
      });
      return Response.success(res, { services });
    } catch (error) {
      console.log("error in concierge");
      console.log(error);
      return Response.error(
        res,
        "INTERNAL_ERROR",
        "Error while fetching concierge services",
        500,
      );
    }
  }

  /** START - Importing Bookings into real booking tables from booking logs */
  async importBookingBulk(req, res) {
    const ids = await Booking.getLogsForImport(500);
    for (let i = 0; i < ids.length; i++) {
      console.log(`Importing: ${ids[i].id}`);
      var success = await this.importBooking(ids[i].id);
      if (success) {
        await Booking.markImported(ids[i].id);
      } else {
        console.log("stopping due to error on " + ids[i].id);
        break;
      }
    }
    return Response.success(res, { message: "Import process completed" });
  }

  async importBooking(id) {
    //id = 15127;
    //id = 14588;
    try {
      const logRecord = await Booking.readBookingLog(id);
      const log = JSON.parse(logRecord[0].log);
      if (!log.data) {
        await Booking.writeTableEntry("bookings_log_errors", {
          log_id: id,
          error: "Empty Booking Log",
        });
        await Booking.markImported(id, 3);
        return true;
      }
      var booking = await this.bookingFromLog(id);
      const uniqueId = booking.booking.uniqueId;
      if (!uniqueId) {
        await Booking.writeTableEntry("bookings_log_errors", {
          log_id: id,
          error: "UniqueId missing",
        });
        await Booking.markImported(id, 3);
        return true;
      }
      const subBookingId = booking.booking.subBookingId;
      var existingBooking = await Booking.bookingByEzeeIds(
        uniqueId,
        subBookingId,
      );
      var booking_id;
      if (existingBooking && existingBooking.length > 0) {
        console.log(`booking exists ${uniqueId} and ${subBookingId}`);
        var dbBooking = {};
        dbBooking["booking"] = existingBooking[0];
        for (const [key] of Object.entries(transactionTables)) {
          const table = transactionTables[key];
          var single = table.type == "single";
          dbBooking[table.tableName] = await Booking.fetchFromTable(
            table.tableName,
            "booking_id",
            dbBooking["booking"].id,
            single,
          );
        }
        booking_id = dbBooking["booking"].id;
        this.compare(booking, dbBooking);
      } else {
        console.log(`insert booking ${uniqueId}`);
        booking_id = await Booking.writeTableEntry("bookings", booking.booking);
        await this.saveTransactionTables(booking, booking_id);
      }
      await this.updateLeadGuest(booking_id, booking.booking.guest_id);
      return true;
    } catch (error) {
      console.log(error);
      await Booking.writeTableEntry("bookings_log_errors", {
        log_id: id,
        error: error,
      });
      return false;
    }
  }

  async bookingFromLog(id) {
    const logRecord = await Booking.readBookingLog(id);
    const log = JSON.parse(logRecord[0].log);
    const reservations = log.data.Reservations;
    const reservation0 = reservations.Reservation[0];
    const bookingTran = reservation0.BookingTran[0];
    bookingTran.property_id = 0;
    const property = await Property.getByEzeeId(bookingTran.RoomTypeCode);
    if (property) {
      bookingTran.property_id = property.id;
    }
    bookingTran.guest_id = await this.guestFromLog(bookingTran, reservation0);
    //bookingTran.guest_id = guest.id;
    var extraFields = { lastOperation: log.operation };
    var booking = {};
    booking["booking"] = Object.assign(
      await this.createFromLog(reservation0, "bookings"),
      await this.createFromLog(bookingTran, "bookings", extraFields),
      await this.createFromLog(log, "bookings"),
    );
    booking = await this.compileTransactionsTables(booking, bookingTran);
    return booking;
  }

  async guestFromLog(log, res = null) {
    var guest = {};
    guest["salutation"] = log.Salutation;
    guest["firstname"] = log.FirstName;
    guest["lastname"] = log.LastName;
    //guest['date_of_birth'] = log.DateOfBirth;
    guest["address_line_1"] = log.Address;
    guest["city"] = log.City;
    guest["state"] = log.State;
    guest["postcode"] = log.Zipcode;
    guest["role"] = 268;
    guest["phone"] = "";
    var mobile = log.Mobile && log.Mobile != "";
    var phone = log.Phone && log.Phone != "";
    if (!mobile && !phone) {
      mobile = res.Mobile && res.Mobile != "";
      phone = res.Phone && res.Phone != "";
    }
    if (mobile || phone) {
      if (mobile) {
        guest["phone"] = await this.prunePhoneNumber(log.Mobile);
        if (phone) {
          guest["phone1"] = await this.prunePhoneNumber(log.Phone);
        }
      } else {
        guest["phone"] = await this.prunePhoneNumber(log.Phone);
      }
    }
    var existingGuest = await Booking.guestFromPhone(
      guest["phone"],
      guest["phone1"],
    );
    if (existingGuest && existingGuest.length > 0) {
      guest = await Booking.updateBookingTable(
        "users",
        guest,
        existingGuest[0].id,
      );
      return existingGuest[0].id;
    } else {
      console.log("NO guest");
      guest = await User.store(guest);
      return guest.id;
    }
  }

  async prunePhoneNumber(number) {
    number = number.replaceAll(" ", "");
    number = number.replaceAll("-", "");
    return number;
  }

  async compare(booking, dbBooking) {
    var updates = [];
    updates = await this.compareSingle(
      booking["booking"],
      dbBooking["booking"],
      "bookings",
      updates,
    );
    for (const [key] of Object.entries(transactionTables)) {
      const table = transactionTables[key];
      if (table.type == "single") {
        updates = await this.compareSingle(
          booking[table.tableName],
          dbBooking[table.tableName],
          table.tableName,
          updates,
        );
      } else {
        //console.log(table.tableName);
        await this.compareMultiple(
          booking[table.tableName],
          dbBooking[table.tableName],
          bookingTables[table.tableName],
          table.tableName,
          dbBooking.booking.id,
        );
      }
    }
    for (let i = 0; i < updates.length; i++) {
      var update = updates[i];
      if (update.table == "bookings") {
        await Booking.updateBookingTable("bookings", update.values, update.id);
      } else {
        await Booking.updateBookingTable(
          update.table,
          update.values,
          update.id,
        );
      }
    }
  }

  async updateLeadGuest(booking_id, guest_id) {
    const clause = `booking_id = ${booking_id} and lead_guest = 1`;
    const leadGuest = await Booking.fetchFromTableWithClause(
      "booking_guests",
      clause,
    );
    if (leadGuest && leadGuest.length > 0) {
      if (guest_id != leadGuest.user_id) {
        Booking.updateBookingTable(
          "booking_guests",
          { user_id: guest_id },
          leadGuest.id,
        );
      }
    } else {
      const guest = {
        booking_id: booking_id,
        user_id: guest_id,
        lead_guest: 1,
      };
      await Booking.writeTableEntry("booking_guests", guest);
    }
  }

  async compareSingle(logTable, dbTable, table, updates) {
    console.log(`Comparing ${table}`);
    //console.log(logTable);
    //console.log(dbTable);
    if (!dbTable) {
      return updates;
    }
    var changed = false;
    var changes = {};
    const fieldMappings = bookingTables[table];
    for (const [key] of Object.entries(fieldMappings)) {
      //console.log(`Field: ${key}=> db: ${dbTable[key]} log: ${logTable[key]}`);
      if (key == "property_id" && !logTable[key]) {
        continue;
      }
      if (dbTable[key] != logTable[key]) {
        changes[key] = logTable[key];
        //console.log(`value of ${key} changed from ${dbTable[key]} to ${logTable[key]}`);
        changed = true;
      }
    }
    if (changed) {
      console.log("--%% changes %%--");
      console.log(changes);
      updates.push({
        type: "update",
        table: table,
        id: dbTable.id,
        values: changes,
      });
    }
    return updates;
  }

  async func1(logTable, dbTable, fieldMappings) {
    for (const [key] of Object.entries(fieldMappings)) {
      if (dbTable[key] != logTable[key]) {
        return false;
      }
    }
    return true;
  }

  async compareMultiple(logTable, dbTable, fieldMappings, table, booking_id) {
    //console.log(fieldMappings);
    if (logTable.length != dbTable.length) {
      if (dbTable.length > 0) {
        await Booking.deleteFromBookingTable(table, booking_id);
      }
      if (logTable.length > 0) {
        for (let i = 0; i < logTable.length; i++) {
          var logRecord = logTable[i];
          logRecord["booking_id"] = booking_id;
          await Booking.writeTableEntry(table, logRecord);
        }
      }
    } else {
      for (let i = 0; i < logTable.length; i++) {
        var matched = false;
        for (let j = 0; j < dbTable.length; j++) {
          if (this.func1(logTable[i], dbTable[j], fieldMappings)) {
            matched = true;
            break;
          }
        }
        if (!matched) {
          if (dbTable.length > 0) {
            await Booking.deleteFromBookingTable(table, booking_id);
          }
          if (logTable.length > 0) {
            for (let i = 0; i < logTable.length; i++) {
              var logRecord = logTable[i];
              logRecord[booking_id] = booking_id;
              await Booking.writeTableEntry(table, logRecord);
            }
          }
        }
      }
    }
  }

  /*async compareMultiple(logTable,dbTable,fieldMappings,updates,booking_id){
        var matchedArray = [];
        for(let i=0;i<logTable.length;i++){
            const matched=false;
            for(let j=0;j<dbTable.length;j++){
                if(this.func1(logTable[i],dbTable[j],fieldMappings)){
                    matched = true;
                    matchedArray.push(dbTable[j].id);
                    break;
                }
            }
            if(!matched){
                //return true;
                var record = logTable[i];
                record['booking_id'] = booking_id;
                updates.push({type:'insert',table:table,values:dbRecord});
            }
        }
        var deletes = dbTable.filter(dbRecord => !matchedArray.includes(dbRecord.id));

        return updates;
    }*/

  async compileTransactionsTables(booking, bookingTran) {
    for (const [key] of Object.entries(transactionTables)) {
      const table = transactionTables[key];
      booking[table.tableName] = [];
      if (table.type == "single") {
        //booking[table.tableName].push(await this.createFromLog(bookingTran,table.tableName));
        booking[table.tableName] = await this.createFromLog(
          bookingTran,
          table.tableName,
        );
      } else {
        booking[table.tableName] = await this.createMultiple(
          bookingTran[table.logElement],
          table.tableName,
        );
      }
    }
    return booking;
  }

  async saveTransactionTables(booking, booking_id) {
    for (const [key] of Object.entries(transactionTables)) {
      const table = transactionTables[key];
      const set = booking[table.tableName];

      if (table.type == "single") {
        if (Object.keys(set).length > 0) {
          //save only if non empty object
          set["booking_id"] = booking_id;
          await Booking.writeTableEntry(table.tableName, set);
        }
      } else {
        for (let i = 0; i < set.length; i++) {
          const record = set[i];
          if (Object.keys(record).length > 0) {
            //save only if non empty object
            record["booking_id"] = booking_id;
            await Booking.writeTableEntry(table.tableName, record);
          }
        }
      }
    }
  }

  async createMultiple(log, table, extraFields = null) {
    //console.log("inside createMultiple");
    var records = [];
    if (!log) {
      console.log(`No ${table} found`);
      return records;
    }
    for (let i = 0; i < log.length; i++) {
      records[i] = await this.createFromLog(log[i], table, extraFields);
    }
    return records;
  }

  async createFromLog(bookingFromLog, table, extraFields = null) {
    const fieldMappings = bookingTables[table];
    var record = {};
    for (const [key, value] of Object.entries(fieldMappings)) {
      if (bookingFromLog[value]) {
        record[key] = bookingFromLog[value];
      }
    }
    if (extraFields) {
      for (var fieldName in extraFields) {
        record[fieldName] = extraFields[fieldName];
      }
    }
    return record;
  }
  /** END - Importing Bookings into real booking tables from booking logs */

  /** START - Admin panel booking screen functions */
  async bookingsList(req, res) {
    try {
      const bookings = await Booking.adminBookingsList();
      await res.render("bookings/list.ejs", { bookings });
    } catch (error) {
      console.error(error);
      return Response.error(
        res,
        "INTERNAL_ERROR",
        "Internal server error",
        500,
      );
    }
  }

  async manageBookingsList(req, res) {
    try {
      const bookings = await Booking.adminManageBookingsList();
      bookings.forEach((booking) => {
        booking["nights"] = differenceInDays(
          new Date(booking.end),
          new Date(booking.start),
        );
        booking["price_per_night"] = (
          booking.totalAmountBeforeTax / booking.nights
        ).toFixed(2);
        booking["net_booking_amount"] =
          booking.totalAmountBeforeTax - booking.taCommision;
      });
      await res.render("bookings/bookings.ejs", { bookings });
    } catch (error) {
      console.error(error);
      return Response.error(
        res,
        "INTERNAL_ERROR",
        "Internal server error",
        500,
      );
    }
  }

  async bookingDetails(req, res) {
    const { id } = req.params; //3350
    try {
      const booking = await SMModel.getRowByUniqueId("bookings", id);
      booking["formated_reservation_date"] = format(
        booking.createDatetime,
        "dd/MM/yyyy",
      );
      booking["formated_start_date"] = format(booking.start, "dd/MM/yyyy");
      booking["formated_end_date"] = format(booking.end, "dd/MM/yyyy");
      booking["nights"] = differenceInDays(booking.end, booking.start);
      const user = await SMModel.getRowByUniqueId("users", booking.guest_id);
      const property = await SMModel.getRowByUniqueId(
        "properties",
        booking.property_id,
      );
      const registration = await SMModel.getRowByUniqueId(
        "booking_registrations",
        id,
        "booking_id",
      );
      const rental = await SMModel.getFromTable("booking_rentalInfo", {
        booking_id: id,
        effectiveDate: format(booking.start, "yyyy-MM-dd"),
      });
      const amenities = await Property.amenitiesByProperty(booking.property_id);
      const property_type = await Setting.display(
        "property_type",
        property.property_type,
      );
      const destination = await SMModel.getRowByUniqueId(
        "destinations",
        property.destination,
      );
      await res.render("bookings/edit.ejs", {
        booking,
        user,
        property,
        registration,
        rental: rental[0],
        amenities,
        property_type,
        destination: destination.name,
      });
    } catch (error) {
      console.log(error);
      return Response.error(
        res,
        "INTERNAL_ERROR",
        "Error while fetching details",
        500,
      );
    }
  }

  async adjustPrices(req, res) {
    const { id } = req.params;
    try {
      const booking = await Booking.bookingWithPricing(id);
      await res.render("bookings/adjust.ejs", { booking });
    } catch (error) {
      console.log(error);
      return Response.error(
        res,
        "INTERNAL_ERROR",
        "Error while fetching details",
        500,
      );
    }
  }
  /** END - Admin panel booking screen functions */

  /** START - block and unblock properties */
  async check(req, res) {
    try {
      const params = {
        RoomID: "4058300000000000003",
        RoomtypeID: "4058300000000000003",
        FromDate: "2025-01-15",
        ToDate: "2025-01-18",
        Reason: "Maintenance Block Test",
      };
      const { status, data } = await this.ezeeHelper.blockProperty(params);
      console.log("logging status and data");
      console.log(status);
      console.log(data);
      return;
    } catch (error) {
      console.log(error);
      return;
    }
  }

  async block(req, res) {
    try {
      const { property_id, start, end, reason, blockType } = req.body;
      if (!req.guest && !req.user) {
        return Response.error(res, "UNAUTHORIZED", "Request not allowed!", 400);
      }
      console.log(req.user);
      var userId;
      if (req.guest) {
        userId = req.guest.id || 0;
      } else {
        userId = req.user.id || 0;
      }

      if (
        await Utility.checkEmpty({ property_id, userId, start, end, blockType })
      ) {
        return Response.error(
          res,
          "MISSING_PARAMETERS",
          "One or more mandatory parameters missing!",
          400,
        );
      }

      // Validate reason field specifically
      if (!reason || reason.trim() === "") {
        return Response.error(
          res,
          "MISSING_REASON",
          "Reason for blocking is required!",
          400,
        );
      }

      console.log("property_id", property_id);

      // First, validate and fix property inventory if needed
      console.log(`Validating property ${property_id} inventory...`);
      const inventoryValidation =
        await roomService.validateAndFixPropertyInventory(property_id);

      if (!inventoryValidation.success) {
        console.log(
          `Failed to validate/fix inventory for property ${property_id}:`,
          inventoryValidation.error,
        );
        return Response.error(
          res,
          "INVENTORY_VALIDATION_FAILED",
          `Property inventory validation failed: ${inventoryValidation.error}`,
        );
      }

      // Get updated inventory
      const inventories = await Property.select("property_inventory", {
        property_id: property_id,
      });
      console.log("inventories", inventories);
      console.log("inventories length", inventories ? inventories.length : 0);

      // Check if there are any rooms in inventory
      if (!inventories || inventories.length === 0) {
        console.log(`No rooms found in inventory for property ${property_id}`);
        return Response.error(
          res,
          "NO_ROOMS_IN_INVENTORY",
          "No rooms available for blocking. Please contact administrator to set up room inventory.",
          400,
        );
      }

      // Filter out invalid/default room entries
      const validInventories = inventories.filter(
        (inventory) =>
          inventory.room_id &&
          inventory.room_type_id &&
          inventory.room_id !== "default_room" &&
          inventory.room_type_id !== "default_room_type" &&
          inventory.room_id.trim() !== "" &&
          inventory.room_type_id.trim() !== "" &&
          inventory.room_id.startsWith("40583"), // Ensure it's a valid Ezee room ID
      );

      if (validInventories.length === 0) {
        console.log(
          `No valid Ezee room IDs found in inventory for property ${property_id}`,
        );
        return Response.error(
          res,
          "INVALID_ROOM_INVENTORY",
          "Property room inventory is not properly configured with Ezee room IDs. Please contact administrator to set up valid room inventory.",
          400,
        );
      }

      const params = [];
      for (let i = 0; i < validInventories.length; i++) {
        const inventory = validInventories[i];

        // Map user reason to Ezee-compatible reason codes
        // Ezee PMS has predefined reason codes, so we need to map user input to valid ones
        let ezeeReason = this.mapToEzeeReasonCode(reason, blockType);
        console.log(
          `Reason mapping: "${reason}" + "${blockType}" -> "${ezeeReason}"`,
        );

        const param = {
          RoomID: inventory.room_id,
          RoomtypeID: inventory.room_type_id,
          FromDate: start,
          ToDate: end,
          Reason: ezeeReason,
        };
        params.push(param);
      }
      console.log(params);

      // Call external PMS to block property with retry for 500 errors
      let ezeeResponse;
      let retryCount = 0;
      const maxRetries = 2;
      let ezeeSuccess = false;

      while (retryCount <= maxRetries) {
        ezeeResponse = await this.ezeeHelper.blockProperty(params);
        console.log("ezeeResponse", ezeeResponse);
        console.log("ezeeResponse.params ----->>>>>", params);

        // If successful or not a 500 error, break out of retry loop
        if (!ezeeResponse || ezeeResponse.status !== 500) {
          ezeeSuccess = true;
          break;
        }

        retryCount++;
        if (retryCount <= maxRetries) {
          console.log(
            `Ezee 500 error, retrying... (${retryCount}/${maxRetries})`,
          );
          // Wait 1 second before retry
          await new Promise((resolve) => setTimeout(resolve, 1000));
        }
      }

      // If Ezee is consistently failing, proceed with local blocking only
      if (!ezeeSuccess) {
        console.log(
          "Ezee PMS is experiencing issues. Proceeding with local blocking only...",
        );
      }

      // Check if ezeeResponse is valid - but allow fallback if Ezee is down
      if (!ezeeResponse || !ezeeResponse.data) {
        console.log(
          "Invalid response from ezeeHelper.blockProperty:",
          ezeeResponse,
        );
        if (!ezeeSuccess) {
          console.log(
            "Ezee PMS is down, proceeding with local blocking only...",
          );
          // Continue with local blocking instead of failing
        } else {
          return Response.error(
            res,
            "FETCH_FAILED",
            "Unable to connect to the PMS system. Please check your internet connection and try again.",
            400,
          );
        }
      }

      // Extract status and data from ezeeResponse if available
      let status, data;
      if (ezeeResponse && ezeeResponse.data) {
        status = ezeeResponse.status;
        data = ezeeResponse.data;

        // Check for HTTP errors (like 500 from Ezee)
        if (status >= 400) {
          console.log("External PMS HTTP error:", status, data);

          // If Ezee is failing, proceed with local blocking only
          if (!ezeeSuccess) {
            console.log(
              "Ezee PMS is down, proceeding with local blocking only...",
            );
            // Continue with local blocking instead of failing
          } else {
            // Handle specific Ezee 500 errors
            if (status === 500) {
              // Check if it's the specific count() error we're seeing
              if (
                data &&
                data.Errors &&
                data.Errors.ErrorMessage &&
                data.Errors.ErrorMessage.includes(
                  "count(): Argument #1 ($value) must be of type Countable|array, string given",
                )
              ) {
                return Response.error(
                  res,
                  "PMS_TECHNICAL_DIFFICULTIES",
                  "The PMS system is experiencing technical difficulties. Please try again in a few minutes or contact support if the issue persists.",
                  400,
                );
              } else {
                return Response.error(
                  res,
                  "PMS_UNAVAILABLE",
                  "The PMS system is temporarily unavailable. Please try again in a few minutes.",
                  400,
                );
              }
            } else if (status === 408 || status === 504) {
              return Response.error(
                res,
                "REQUEST_TIMEOUT",
                "Request timed out. Please try again.",
                400,
              );
            } else {
              return Response.error(
                res,
                "EXTERNAL_SYSTEM_ERROR",
                "Failed to communicate with external system. Please try again.",
                400,
              );
            }
            return;
          }
        }
      }

      // Check for errors in the response data - but allow fallback if Ezee is down
      if (
        ezeeResponse &&
        ezeeResponse.data &&
        data &&
        data.Errors &&
        data.Errors.ErrorCode &&
        data.Errors.ErrorCode != 0
      ) {
        console.log("External PMS error:", data.Errors.ErrorMessage);

        // If Ezee is failing, proceed with local blocking only
        if (!ezeeSuccess) {
          console.log(
            "Ezee PMS is down, proceeding with local blocking only...",
          );
          // Continue with local blocking instead of failing
        } else {
          // Check if it's a reason code error and provide helpful message
          if (
            data.Errors.ErrorMessage &&
            data.Errors.ErrorMessage.includes(
              "Room block reason is not available",
            )
          ) {
return Response.error(  
              res,
              "INVALID_BLOCK_REASON",
              "The reason you provided is not recognized by the PMS system. Please try using: Maintenance, Owner Block, Out of Order, or Emergency.",
              400,
            );
          } else {
            return Response.error(
              res,
              "EXTERNAL_SYSTEM_ERROR",
              `External system error: ${data.Errors.ErrorMessage}`,
              400,
            );
          }
        }
      }

      // Only check status if we have a valid Ezee response
      if (ezeeResponse && ezeeResponse.data && ezeeResponse.status != 200) {
        console.log(
          "External PMS returned non-200 status:",
          ezeeResponse.status,
        );
        if (!ezeeSuccess) {
          console.log(
            "Ezee PMS is down, proceeding with local blocking only...",
          );
          // Continue with local blocking instead of failing
        } else {
          return Response.error(
            res,
            "EXTERNAL_SYSTEM_ERROR",
            "Failed to block property in external system. Please try again.",
            400,
          );

        }
      }
      const blocks = [];
      const datesToBeBlocked = eachDayOfInterval({
        start: start,
        end: end,
      });
      for (let i = 0; i < inventories.length; i++) {
        const inventory = inventories[i];
        const booking = {
          uniqueId: 0,
          subBookingId: 0,
          property_id: property_id,
          roomTypeCode: inventory.room_type_id,
          roomId: inventory.room_id,
          start: start,
          end: end,
          currentStatus: "Block",
          status: blockType,
          comment: reason,
          guest_id: 0,
          isConfirmed: 0,
          arrivalTime: 0,
          departureTime: 0,
          bookedBy: 0,
          isChannelBooking: 0,
        };
        const block_id = await Booking.insert("bookings", booking);
        for (let j = 0; j < datesToBeBlocked.length; j++) {
          const effectiveDate = format(datesToBeBlocked[j], "yyyy-MM-dd");
          await Booking.insert("booking_rentalInfo", {
            booking_id: block_id,
            effectiveDate: effectiveDate,
          });
          await Booking.insert("temp_RentalInfo", {
            block_id: block_id,
            uniqueId: 0,
            subBookingId: 0,
            EffectiveDate: effectiveDate,
            RoomTypeCode: inventory.room_type_id,
            RoomId: inventory.room_id,
            Discount: 0.0,
            Rent: 0,
            Adult: 0,
            Child: 0,
            PackageCode: 0,
            PackageName: 0,
            RoomTypeName: 0,
          });
        }
        blocks.push({
          block_id,
          property: inventory.room_type_id,
          start,
          end,
          room_id: inventory.room_id,
          blockType,
        });
      }
      // Calculate potential revenue loss
      const revenueLoss = await this.calculateRevenueLoss(
        property_id,
        start,
        end,
      );

      // Prepare success message
      let successMessage = `Dates blocked successfully. Potential revenue loss: ₹${revenueLoss.totalLoss}`;
      if (!ezeeSuccess) {
        successMessage = `Dates blocked successfully (local only - PMS system is temporarily unavailable). Potential revenue loss: ₹${revenueLoss.totalLoss}`;
      }

      return Response.success(
        res,
        {
          success: true,
          blocks,
          revenueLoss: revenueLoss,
          message: successMessage,
        },
        200
      );
      return;
    } catch (error) {
      console.log(error);
      return Response.error(
        res,
        "BLOCKING_ERROR",
        "Error while blocking property",
        400,
      );
    }
  }

  async unblock(req, res) {
    try {
      const { block_id } = req.body;
      if (!block_id) {
        console.log("block_id missing");
        return Response.error(
          res,
          "MISSING_PARAMETERS",
          "Mandatory parameters missing or incorrect!",
          400,
        );
      }
      console.log("block_id found");
      const block = await Booking.find("bookings", {
        id: block_id,
        currentStatus: "Block",
      });
      if (!block) {
        console.log("block missing");
        return Response.error(
          res,
          "BLOCK_NOT_FOUND",
          "Block not found",
          400,
        );

      }
      console.log("block found");
      const params = [];
      const param = {
        RoomID: block.roomId,
        RoomtypeID: block.roomTypeCode,
        FromDate: format(block.start, "yyyy-MM-dd"),
        ToDate: format(block.end, "yyyy-MM-dd"),
        Reason: block.status,
      };
      params.push(param);

      // Try Ezee unblock with retry for 500 errors
      let ezeeResponse;
      let retryCount = 0;
      const maxRetries = 2;
      let ezeeSuccess = false;

      while (retryCount <= maxRetries) {
        ezeeResponse = await this.ezeeHelper.unblockProperty(params);
        console.log("data after ezee call");
        console.log(ezeeResponse);

        // If successful or not a 500 error, break out of retry loop
        if (!ezeeResponse || ezeeResponse.status !== 500) {
          break;
        }

        retryCount++;
        if (retryCount <= maxRetries) {
          console.log(
            `Ezee 500 error during unblock, retrying... (${retryCount}/${maxRetries})`,
          );
          // Wait 1 second before retry
          await new Promise((resolve) => setTimeout(resolve, 1000));
        }
      }

      // If Ezee is consistently failing, proceed with local unblocking only
      if (!ezeeSuccess) {
        console.log(
          "Ezee PMS is experiencing issues during unblock. Proceeding with local unblocking only...",
        );
      }

      // Check Ezee response only if we have a valid response
      if (ezeeResponse && ezeeResponse.data) {
        const { status, data } = ezeeResponse;

        if (data.Errors.ErrorCode && data.Errors.ErrorCode != 0) {
          // Check if it's the "no block" error (expected when block was created locally only)
          if (
            data.Errors.ErrorMessage &&
            data.Errors.ErrorMessage.includes("There is no block on this room")
          ) {
            console.log(
              "Block was created locally only, proceeding with local unblock...",
            );
            ezeeSuccess = false; // We'll proceed with local unblock
          } else if (!ezeeSuccess) {
            console.log(
              "Ezee PMS is down, proceeding with local unblocking only...",
            );
            // Continue with local unblocking instead of failing
          } else {
            return Response.error(
              res,
              "UNBLOCKING_ERROR",
              data.Errors.ErrorMessage,
              400
            );
          }
        } else if (status === 200) {
          ezeeSuccess = true;
        }

        if (status != 200) {
          if (!ezeeSuccess) {
            console.log(
              "Ezee PMS is down, proceeding with local unblocking only...",
            );
            // Continue with local unblocking instead of failing
          } else {
            return Response.error(
              res,
              "UNBLOCKING_ERROR",
              "Error while unblocking property",
              400
            );
          }
        }
      }
      // Calculate revenue recovery
      const revenueRecovery = await this.calculateRevenueLoss(
        block.property_id,
        format(block.start, "yyyy-MM-dd"),
        format(block.end, "yyyy-MM-dd"),
      );

      await Booking.delete("bookings", { id: block_id });
      await Booking.delete("booking_rentalInfo", { booking_id: block_id });
      await Booking.delete("temp_RentalInfo", { block_id: block_id });

      // Prepare success message
      let successMessage = `Dates unblocked successfully. Potential revenue recovery: ₹${revenueRecovery.totalLoss}`;
      if (!ezeeSuccess) {
        successMessage = `Dates unblocked successfully (local only - PMS system is temporarily unavailable). Potential revenue recovery: ₹${revenueRecovery.totalLoss}`;
      }

      return Response.success(
        res,
        {
          success: true,
          revenueRecovery: revenueRecovery,
          message: successMessage,
        },
        200
      );
    } catch (error) {
      console.log(error);
      return Response.error(
        res,
        "UNBLOCKING_ERROR",
        "Error while unblocking property",
        400
      );
    }
  }
  /** END - block and unblock properties */

  async bookingDetailsForHost(req, res) {
    const { booking_id } = req.body;
    try {
      const { booking, rentalInfo, tariff, property, guest } =
        await Booking.bookingDetailsForHost(booking_id);
      return Response.success(res, { booking, rentalInfo, tariff, property, guest }, 200);
    } catch (error) {
      console.log(error);
      return Response.error(
        res,
        "FETCHING_DETAILS_ERROR",
        "Error while fetching details",
        400
      );
    }
  }

  async removeBlock(req, res) {
    const { id } = req.params;
    try {
      const booking = await Booking.bookingWithPricing(id);
      if (!booking) {
        return Response.error(
          res,
          "BOOKING_NOT_FOUND",
          "Booking not found",
          400
        );
      }
      await Booking.delete("bookings", { id });
      await Booking.delete("booking_rentalInfo", { booking_id: id });
      await Booking.delete("temp_RentalInfo", { block_id: id });
      return Response.success(res, { success: true }, 200);
    } catch (error) {
      console.log(error);
      return Response.error(
        res,
        "REMOVING_BLOCK_ERROR",
        "Error while removing block",
        400
      );
    }
  }

  // Calculate potential revenue loss for blocked dates
  async calculateRevenueLoss(propertyId, startDate, endDate) {
    try {
      // Get property details
      const property = await Property.find("properties", { id: propertyId });
      if (!property) {
        return { totalLoss: 0, dailyBreakdown: [], averageRate: 0 };
      }

      // Get historical pricing data for similar dates
      const historicalData = await Booking.getHistoricalPricing(
        propertyId,
        startDate,
        endDate,
      );

      // Calculate number of nights
      const startDateObj = new Date(startDate);
      const endDateObj = new Date(endDate);
      const timeDiff = endDateObj.getTime() - startDateObj.getTime();
      const numberOfNights = Math.ceil(timeDiff / (1000 * 3600 * 24));

      let totalLoss = 0;
      let averageRate = 0;
      const dailyBreakdown = [];

      if (historicalData && historicalData.length > 0) {
        // Use historical average
        averageRate =
          historicalData.reduce((sum, record) => sum + record.avgRate, 0) /
          historicalData.length;
      } else {
        // Fallback to market average or property base rate
        averageRate = await this.getMarketAverageRate(propertyId);
      }

      // Calculate daily breakdown
      for (let i = 0; i < numberOfNights; i++) {
        const currentDate = new Date(startDateObj);
        currentDate.setDate(startDateObj.getDate() + i);

        // Apply seasonal adjustments
        const seasonalMultiplier = this.getSeasonalMultiplier(currentDate);
        const dailyRate = Math.round(averageRate * seasonalMultiplier);

        totalLoss += dailyRate;
        dailyBreakdown.push({
          date: currentDate.toISOString().split("T")[0],
          potentialRate: dailyRate,
          dayOfWeek: currentDate.toLocaleDateString("en-US", {
            weekday: "long",
          }),
        });
      }

      return {
        totalLoss: Math.round(totalLoss),
        averageRate: Math.round(averageRate),
        numberOfNights,
        dailyBreakdown,
        propertyName: property.listing_name,
      };
    } catch (error) {
      console.error("Error calculating revenue loss:", error);
      return { totalLoss: 0, dailyBreakdown: [], averageRate: 0 };
    }
  }

  // Get market average rate for property
  async getMarketAverageRate(propertyId) {
    try {
      const property = await Property.find("properties", { id: propertyId });
      // Use property-specific base rate or market average
      // This could be enhanced with more sophisticated pricing logic
      return property?.base_price || 3000; // Default fallback rate
    } catch (error) {
      return 3000; // Default fallback rate
    }
  }

  // Apply seasonal multipliers
  getSeasonalMultiplier(date) {
    const month = date.getMonth() + 1; // 1-12
    const dayOfWeek = date.getDay(); // 0-6 (Sunday = 0)

    let multiplier = 1.0;

    // Seasonal adjustments
    if (month >= 11 || month <= 2) {
      // Winter season
      multiplier *= 1.2;
    } else if (month >= 6 && month <= 8) {
      // Summer season
      multiplier *= 1.1;
    }

    // Weekend premium
    if (dayOfWeek === 5 || dayOfWeek === 6) {
      // Friday, Saturday
      multiplier *= 1.15;
    }

    return multiplier;
  }

  // Public API: estimate revenue loss for a block window (no side effects)
  async revenueLossEstimate(req, res) {
    try {
      const { property_id, start, end } = req.body;

      // --- Required Params ---
      if (!property_id || !start || !end) {
        return Response.error(
          res,
          "MISSING_MANDATORY_PARAMETERS",
          "Mandatory parameters missing or incorrect!",
          400
        );
      }

      // --- Date Validation ---
      const startDate = new Date(start);
      const endDate = new Date(end);

      if (
        Number.isNaN(startDate.getTime()) ||
        Number.isNaN(endDate.getTime())
      ) {
        return Response.error(
          res,
          "INVALID_DATE_FORMAT",
          "Invalid date format",
          400
        );
      }

      if (endDate < startDate) {
        return Response.error(
          res,
          "END_DATE_BEFORE_START_DATE",
          "End date cannot be before start date",
          400
        );
      }

      // --- Normalize Dates ---
      const normalizedStart = format(startDate, "yyyy-MM-dd");
      const normalizedEnd = format(endDate, "yyyy-MM-dd");

      // --- Calculate Loss (use live Ezee pricing like availabilityAndDetails) ---
      const revenueLoss = await this.calculateRevenueLossFromEzee(
        property_id,
        normalizedStart,
        normalizedEnd,
      );

      // --- Normalize Response Structure ---
      const totalLoss =
        revenueLoss?.totalLoss ??
        revenueLoss?.estimatedLoss ??
        revenueLoss?.estimated_loss ??
        revenueLoss?.loss ??
        0;

      return res.status(200).json({
        success: true,
        estimatedLoss: typeof totalLoss === "number" ? totalLoss : 0,
        revenueLoss,
        meta: {
          property_id,
          start: normalizedStart,
          end: normalizedEnd,
        },
      });
    } catch (error) {
      console.error("❌ Error estimating revenue loss:", error);

      return res.status(500).json({
        success: false,
        error: "Error calculating revenue loss",
        estimatedLoss: 0,
      });
    }
  }

  // Ezee-driven revenue loss using live avg_per_night_without_tax (with web discount)
  async calculateRevenueLossFromEzee(propertyId, startDate, endDate) {
    try {
      const property = await Property.find("properties", { id: propertyId });
      if (!property || !this.ezeeHelper || !property.channel_id) {
        return null;
      }

      const startObj = new Date(startDate);
      const endObj = new Date(endDate);

      // Align with availabilityAndDetails: nights are check-in inclusive, check-out exclusive
      const endExclusive = addDays(endObj, -1);
      if (endExclusive < startObj) {
        return null;
      }

      const stayNights = eachDayOfInterval({
        start: startObj,
        end: endExclusive,
      });
      if (stayNights.length === 0) {
        return null;
      }

      const ezeeResp = await this.ezeeHelper.getProperty({
        check_in_date: startDate,
        check_out_date: endDate,
        roomtypeunkid: property.channel_id,
      });
      const ezeeProp = ezeeResp?.property;
      const baseRate = ezeeProp?.room_rates_info?.avg_per_night_without_tax;
      if (!baseRate || Number.isNaN(baseRate)) {
        return null;
      }

      const webDiscount = await SettingsHelper.webDiscount();
      const pricePerNight = Math.round((baseRate * (100 - webDiscount)) / 100);

      const dailyBreakdown = stayNights.map((date) => ({
        date: format(date, "yyyy-MM-dd"),
        potentialRate: pricePerNight,
        dayOfWeek: date.toLocaleDateString("en-US", { weekday: "long" }),
      }));

      const totalLoss = pricePerNight * stayNights.length;

      return {
        totalLoss: Math.round(totalLoss),
        averageRate: Math.round(pricePerNight),
        numberOfNights: stayNights.length,
        dailyBreakdown,
        propertyName: property.listing_name,
        source: "ezee",
      };
    } catch (error) {
      console.log(
        "Ezee revenue loss calc failed, falling back",
        error?.message || error,
      );
      return null;
    }
  }
}
module.exports = BookingsController;
