const hotelSearchService = require("../services/newSearchService");

/* ----------------------------------------
   CONSTANTS
---------------------------------------- */

const VALID_CITY_CODES = new Set([
  // India
  "BLR", "DEL", "BOM", "MAA", "HYD", "CCU", "GOI", "PNQ", "AMD", "JAI",
  "COK", "TRV", "IXC", "ATQ", "LKO", "BHO", "NAG", "STV", "VTZ", "IXB",
  "GAU", "IXR", "BBI", "PAT", "VNS", "IXA", "IMF", "DIB", "JDH", "UDR",
  "IDR", "IXZ", "DED", "IXM", "IXE", "MYQ", "HJR", "JLR", "RPR", "RAJ",
  "AGR", "SXR", "IXL", "KNU", "IXD", "IXH", "PGH", "JRH", "MZU", "BDQ",
  // International - Asia
  "DXB", "AUH", "DOH", "KWI", "BAH", "MCT", "RUH", "JED", "SIN", "KUL",
  "BKK", "HKG", "NRT", "KIX", "ICN", "PEK", "PVG", "CGK", "MNL", "CMB",
  "DAC", "KTM", "RGN", "SGN", "HAN", "TPE",
  // International - Europe
  "LHR", "CDG", "FRA", "AMS", "MAD", "BCN", "FCO", "MXP", "ZRH", "VIE",
  "BRU", "CPH", "ARN", "OSL", "HEL", "LIS", "ATH", "WAW", "PRG", "BUD",
  // International - Americas
  "JFK", "LAX", "ORD", "DFW", "MIA", "SFO", "SEA", "BOS", "YYZ", "YVR",
  "GRU", "BOG", "LIM", "SCL", "EZE", "MEX",
  // International - Africa & Oceania
  "JNB", "CPT", "CAI", "NBO", "LOS", "CMN", "SYD", "MEL", "BNE", "AKL"
]);

const VALID_CHAIN_CODES = new Set([
  "LC", "MC", "HI", "HY", "TJ", "BR", "WI", "CN", "RD", "NO",
  "HL", "TT", "OX", "FN", "AL", "WV", "HQ", "FG", "HZ", "SX",
  "YX", "ST", "CP", "RT", "SI", "BW", "IH", "RM", "AK", "CY",
  "RZ", "SH", "UI", "XR", "ZI", "EM", "EC", "EH", "FS", "GI",
  "IC", "IN", "KI", "LQ", "LW", "MA", "MT", "NV", "PH", "PR",
  "RA", "RC", "SG", "SK", "SU", "SW", "TX", "UT", "VV", "WB"
]);

const VALID_SORT_OPTIONS  = ["PRICE_LOW", "PRICE_HIGH", "NAME", "DISTANCE"];
const DATE_REGEX          = /^\d{4}-\d{2}-\d{2}$/;
const MAX_NIGHTS          = 30;
const MAX_ADVANCE_DAYS    = 365;
const MAX_ROOMS           = 9;
// Per Travelport UAPI docs:
// Max 4 adults, max 5 children, max 9 total occupants
const MAX_ADULTS        = 4;
const MAX_CHILDREN      = 5;
const MAX_TOTAL_OCCUPANTS = 9;

/* ----------------------------------------
   HELPER - Send validation error
---------------------------------------- */

const validationError = (res, field, message) => {
  return res.status(400).json({
    status:  "error",
    type:    "ValidationError",
    field,
    message
  });
};

/* ----------------------------------------
   MAIN CONTROLLER
---------------------------------------- */

exports.hotelSearch = async (req, res) => {

  console.log("====================================");
  console.log("HOTEL SEARCH CONTROLLER START");
  console.log("Incoming Request:", JSON.stringify(req.body, null, 2));
  console.log("====================================");

  try {

    const {
      cityCode,
      location,
      checkinDate,
      checkoutDate,
      adults         = 1,
      rooms          = 1,
      availableHotelsOnly = false,
      returnAmenities     = false,
      permittedChains     = [],
      amenities           = [],
      sort           = "PRICE_LOW",
      page           = 1,
      limit          = 100,
      filters        = {}
    } = req.body;

    /* ----------------------------------------
       1. CITY CODE
    ---------------------------------------- */

    const rawCode = cityCode || location;

    if (!rawCode) {
      return validationError(res, "cityCode", "cityCode or location is required");
    }

    if (typeof rawCode !== "string") {
      return validationError(res, "cityCode", "cityCode must be a string");
    }

    const upperCityCode = rawCode.trim().toUpperCase();

    if (upperCityCode.length === 0) {
      return validationError(res, "cityCode", "cityCode cannot be empty");
    }

    if (!/^[A-Z]{3}$/.test(upperCityCode)) {
      return validationError(
        res, "cityCode",
        `cityCode must be exactly 3 letters (e.g. BLR, DEL, BOM). Received: "${rawCode}"`
      );
    }

    if (!VALID_CITY_CODES.has(upperCityCode)) {
      return validationError(
        res, "cityCode",
        `"${upperCityCode}" is not a supported city code. Examples: BLR, DEL, BOM, HYD, MAA, DXB, LHR`
      );
    }

    /* ----------------------------------------
       2. CHECKIN DATE
    ---------------------------------------- */

    if (!checkinDate) {
      return validationError(res, "checkinDate", "checkinDate is required");
    }

    if (typeof checkinDate !== "string") {
      return validationError(res, "checkinDate", "checkinDate must be a string");
    }

    if (!DATE_REGEX.test(checkinDate.trim())) {
      return validationError(
        res, "checkinDate",
        `checkinDate must be in YYYY-MM-DD format (e.g. 2026-04-10). Received: "${checkinDate}"`
      );
    }

    const checkinObj = new Date(checkinDate);

    if (isNaN(checkinObj.getTime())) {
      return validationError(res, "checkinDate", `"${checkinDate}" is not a valid date`);
    }

    const today = new Date();
    today.setHours(0, 0, 0, 0);

    if (checkinObj < today) {
      return validationError(
        res, "checkinDate",
        `checkinDate cannot be in the past. Received: "${checkinDate}"`
      );
    }

    const maxCheckin = new Date(today);
    maxCheckin.setDate(maxCheckin.getDate() + MAX_ADVANCE_DAYS);

    if (checkinObj > maxCheckin) {
      return validationError(
        res, "checkinDate",
        `checkinDate cannot be more than ${MAX_ADVANCE_DAYS} days in advance`
      );
    }

    /* ----------------------------------------
       3. CHECKOUT DATE
    ---------------------------------------- */

    if (!checkoutDate) {
      return validationError(res, "checkoutDate", "checkoutDate is required");
    }

    if (typeof checkoutDate !== "string") {
      return validationError(res, "checkoutDate", "checkoutDate must be a string");
    }

    if (!DATE_REGEX.test(checkoutDate.trim())) {
      return validationError(
        res, "checkoutDate",
        `checkoutDate must be in YYYY-MM-DD format (e.g. 2026-04-13). Received: "${checkoutDate}"`
      );
    }

    const checkoutObj = new Date(checkoutDate);

    if (isNaN(checkoutObj.getTime())) {
      return validationError(res, "checkoutDate", `"${checkoutDate}" is not a valid date`);
    }

    if (checkoutObj <= checkinObj) {
      return validationError(
        res, "checkoutDate",
        `checkoutDate must be after checkinDate. checkin: "${checkinDate}", checkout: "${checkoutDate}"`
      );
    }

    const nights = (checkoutObj - checkinObj) / (1000 * 60 * 60 * 24);

    if (nights > MAX_NIGHTS) {
      return validationError(
        res, "checkoutDate",
        `Maximum stay is ${MAX_NIGHTS} nights. Requested: ${nights} nights`
      );
    }

    /* ----------------------------------------
       4. ADULTS
    ---------------------------------------- */

    /*const adultsNum = Number(adults);

    if (isNaN(adultsNum) || !Number.isInteger(adultsNum)) {
      return validationError(res, "adults", "adults must be a whole number");
    }

    if (adultsNum < 1 || adultsNum > MAX_ADULTS) {
      return validationError(
        res, "adults",
        `adults must be between 1 and ${MAX_ADULTS}. Received: ${adults}`
      );
    }

    /* ----------------------------------------
       5. ROOMS
    ---------------------------------------- */

    /*const roomsNum = Number(rooms);

    if (isNaN(roomsNum) || !Number.isInteger(roomsNum)) {
      return validationError(res, "rooms", "rooms must be a whole number");
    }

    if (roomsNum < 1 || roomsNum > MAX_ROOMS) {
      return validationError(
        res, "rooms",
        `rooms must be between 1 and ${MAX_ROOMS}. Received: ${rooms}`
      );
    }*/

    /* ----------------------------------------
       6. ADULTS vs ROOMS
    ---------------------------------------- */

   /* if (adultsNum < roomsNum) {
      return validationError(
        res, "adults",
        `adults (${adultsNum}) cannot be less than rooms (${roomsNum})`
      );
    }*/


/* ----------------------------------------
       4. ADULTS VALIDATION
       Max 4 adults per Travelport UAPI docs
    ---------------------------------------- */

    const adultsNum = Number(adults);

    if (isNaN(adultsNum) || !Number.isInteger(adultsNum)) {
      return validationError(res, "adults", "adults must be a whole number");
    }

    if (adultsNum < 1 || adultsNum > MAX_ADULTS) {
      return validationError(
        res, "adults",
        `adults must be between 1 and ${MAX_ADULTS} per Travelport UAPI specification. Received: ${adults}`
      );
    }

    /* ----------------------------------------
       5. ROOMS VALIDATION
    ---------------------------------------- */

    const roomsNum = Number(rooms);

    if (isNaN(roomsNum) || !Number.isInteger(roomsNum)) {
      return validationError(res, "rooms", "rooms must be a whole number");
    }

    if (roomsNum < 1 || roomsNum > MAX_ROOMS) {
      return validationError(
        res, "rooms",
        `rooms must be between 1 and ${MAX_ROOMS}. Received: ${rooms}`
      );
    }

    /* ----------------------------------------
       6. ADULTS vs ROOMS
    ---------------------------------------- */

    if (adultsNum < roomsNum) {
      return validationError(
        res, "adults",
        `adults (${adultsNum}) cannot be less than rooms (${roomsNum})`
      );
    }

    /* ----------------------------------------
       6B. CHILDREN VALIDATION
       Per Travelport UAPI docs:
       - Max 5 children
       - Each child MUST have an age (Count + Age required)
       - Age must be provided for every child listed
       - Total occupants (adults + children) max 9
       - Galileo (1G) supports NumberOfChildren with Age
       - Worldspan does NOT support Age for children
    ---------------------------------------- */

    const children = req.body.children;

    let childrenCount = 0;

    if (children !== undefined && children !== null) {

      // Must be an array
      if (!Array.isArray(children)) {
        return validationError(
          res, "children",
          "children must be an array of objects. e.g. [{ age: 5 }, { age: 10 }]"
        );
      }

      childrenCount = children.length;

      // Max 5 children per Travelport UAPI docs
      if (childrenCount > MAX_CHILDREN) {
        return validationError(
          res, "children",
          `Maximum ${MAX_CHILDREN} children allowed per Travelport UAPI specification. Received: ${childrenCount}`
        );
      }

      // Total occupants check — max 9 per UAPI docs
      const totalOccupants = adultsNum + childrenCount;

      if (totalOccupants > MAX_TOTAL_OCCUPANTS) {
        return validationError(
          res, "children",
          `Total occupants (adults + children) cannot exceed ${MAX_TOTAL_OCCUPANTS} per Travelport UAPI specification. adults: ${adultsNum}, children: ${childrenCount}, total: ${totalOccupants}`
        );
      }

      // Validate each child object
      for (let i = 0; i < children.length; i++) {

        const child = children[i];

        // Must be a plain object
        if (typeof child !== "object" || child === null || Array.isArray(child)) {
          return validationError(
            res, `children[${i}]`,
            `Each child must be an object with an age. e.g. { "age": 5 }. Invalid at index ${i}`
          );
        }

        // Age is REQUIRED per UAPI docs
        // "The child count must include one age for each child listed"
        if (child.age === undefined || child.age === null || child.age === "") {
          return validationError(
            res, `children[${i}].age`,
            `Age is required for every child per Travelport UAPI specification. Missing at index ${i}. e.g. { "age": 5 }`
          );
        }

        const childAge = Number(child.age);

        // Must be a number
        if (isNaN(childAge)) {
          return validationError(
            res, `children[${i}].age`,
            `Age must be a number. Invalid value "${child.age}" at index ${i}`
          );
        }

        // Must be a whole number
        if (!Number.isInteger(childAge)) {
          return validationError(
            res, `children[${i}].age`,
            `Age must be a whole number. Received "${child.age}" at index ${i}`
          );
        }

        // Age cannot be negative
        if (childAge < 0) {
          return validationError(
            res, `children[${i}].age`,
            `Age cannot be negative. Received ${childAge} at index ${i}`
          );
        }

        // Must be 17 or below — 18+ must be counted as adult
        if (childAge > 17) {
          return validationError(
            res, `children[${i}].age`,
            `Child age must be 17 or below. Received ${childAge} at index ${i}. Guests aged 18 or above must be counted as adults`
          );
        }
      }
    }

    /* ----------------------------------------
       7. PERMITTED CHAINS
    ---------------------------------------- */



    if (!Array.isArray(permittedChains)) {
      return validationError(res, "permittedChains", "permittedChains must be an array");
    }

    if (permittedChains.length > 0) {

      for (const chain of permittedChains) {

        if (typeof chain !== "string") {
          return validationError(
            res, "permittedChains",
            `Each chain code must be a string. Invalid value: "${chain}"`
          );
        }

        if (!/^[A-Z]{2}$/.test(chain.trim().toUpperCase())) {
          return validationError(
            res, "permittedChains",
            `Chain code must be exactly 2 letters. Invalid: "${chain}"`
          );
        }

        if (!VALID_CHAIN_CODES.has(chain.trim().toUpperCase())) {
          return validationError(
            res, "permittedChains",
            `"${chain}" is not a valid chain code. Examples: LC, MC, HI, HY, HL, RD, WI`
          );
        }
      }
    }

    /* ----------------------------------------
       8. SORT
    ---------------------------------------- */

    if (!VALID_SORT_OPTIONS.includes(sort)) {
      return validationError(
        res, "sort",
        `sort must be one of: ${VALID_SORT_OPTIONS.join(", ")}. Received: "${sort}"`
      );
    }

    /* ----------------------------------------
       9. PAGE & LIMIT
    ---------------------------------------- */

    const pageNum  = Number(page);
    const limitNum = Number(limit);

    if (isNaN(pageNum) || !Number.isInteger(pageNum) || pageNum < 1) {
      return validationError(res, "page", "page must be a positive integer");
    }

    if (isNaN(limitNum) || !Number.isInteger(limitNum) || limitNum < 1 || limitNum > 200) {
      return validationError(res, "limit", "limit must be a whole number between 1 and 200");
    }

    /* ----------------------------------------
       10. FILTERS
    ---------------------------------------- */

    if (typeof filters !== "object" || Array.isArray(filters)) {
      return validationError(res, "filters", "filters must be an object");
    }

    if (filters.priceMin !== undefined) {

      const min = Number(filters.priceMin);

      if (isNaN(min) || min < 0) {
        return validationError(res, "filters.priceMin", "priceMin must be a positive number");
      }
    }

    if (filters.priceMax !== undefined) {

      const max = Number(filters.priceMax);

      if (isNaN(max) || max < 0) {
        return validationError(res, "filters.priceMax", "priceMax must be a positive number");
      }
    }

    if (
      filters.priceMin !== undefined &&
      filters.priceMax !== undefined &&
      Number(filters.priceMin) > Number(filters.priceMax)
    ) {
      return validationError(
        res, "filters.priceMin",
        `priceMin (${filters.priceMin}) cannot be greater than priceMax (${filters.priceMax})`
      );
    }

    if (filters.stars !== undefined) {

      if (!Array.isArray(filters.stars)) {
        return validationError(res, "filters.stars", "stars must be an array e.g. [3, 4, 5]");
      }

      if (filters.stars.length === 0) {
        return validationError(res, "filters.stars", "stars array cannot be empty");
      }

      const invalidStars = filters.stars.filter(s => ![1, 2, 3, 4, 5].includes(Number(s)));

      if (invalidStars.length > 0) {
        return validationError(
          res, "filters.stars",
          `Invalid star values: ${invalidStars.join(", ")}. Must be between 1 and 5`
        );
      }
    }

    if (filters.chain !== undefined) {

      if (typeof filters.chain !== "string") {
        return validationError(res, "filters.chain", "filters.chain must be a string");
      }

      const chainUpper = filters.chain.trim().toUpperCase();

      if (!VALID_CHAIN_CODES.has(chainUpper)) {
        return validationError(
          res, "filters.chain",
          `"${chainUpper}" is not a valid chain code. Examples: LC, MC, HI, HY, HL, RD`
        );
      }
    }

    /* ----------------------------------------
       11. BOOLEAN FLAGS
    ---------------------------------------- */

    if (typeof availableHotelsOnly !== "boolean") {
      return validationError(
        res, "availableHotelsOnly",
        "availableHotelsOnly must be true or false"
      );
    }

    if (typeof returnAmenities !== "boolean") {
      return validationError(
        res, "returnAmenities",
        "returnAmenities must be true or false"
      );
    }

    /* ----------------------------------------
       12. AMENITIES
    ---------------------------------------- */

    if (!Array.isArray(amenities)) {
      return validationError(res, "amenities", "amenities must be an array");
    }

    /* ----------------------------------------
       CALL SERVICE
    ---------------------------------------- */

    const result = await hotelSearchService.searchHotels({
      ...req.body,
      cityCode: upperCityCode  // always pass uppercased city code
    });

    console.log("====================================");
    console.log("HOTEL SEARCH CONTROLLER SUCCESS");
    console.log(`Total Results: ${result.totalResults}`);
    console.log("====================================");

    return res.status(200).json({
      status:             "success",
      totalResults:       result.totalResults,
      hotelSearchResults: result.hotelSearchResults
    });

  } catch (error) {

    console.error("====================================");
    console.error("HOTEL SEARCH CONTROLLER ERROR");
    console.error("Error:", error.message);
    console.error("====================================");

    if (error.message?.includes("SOAP")) {
      return res.status(502).json({
        status:  "error",
        type:    "TravelportError",
        message: "Travelport service error. Please try again.",
        detail:  error.message
      });
    }

    if (error.code === "ECONNABORTED") {
      return res.status(504).json({
        status:  "error",
        type:    "TimeoutError",
        message: "Request timed out. Travelport did not respond in time."
      });
    }

    if (error.code === "ECONNREFUSED" || error.code === "ENOTFOUND") {
      return res.status(503).json({
        status:  "error",
        type:    "ServiceUnavailable",
        message: "Unable to connect to Travelport service."
      });
    }

    return res.status(500).json({
      status:  "error",
      type:    "InternalServerError",
      message: error.message || "Something went wrong. Please try again."
    });
  }
};