import type { Context } from "hono";
import { readFileSync } from "fs";
import { resolve } from "path";

// ═══════════════════════════════════════════════════════════════════════════════
//  Types
// ═══════════════════════════════════════════════════════════════════════════════

interface HotDealOffer {
  resort: string;
  travel_between: [string, string][];
  new?: string;
  starts: string;
  expires: string;
  roles?: any[];
  unit_code: string[];
  night: number;
  occ_up_to?: number;
  occp?: number;
  price?: number;
  occp_price?: Record<string, any>;
  occp_priceXXX?: Record<string, any>;
  inclusions?: string;
  inclusions_cost_min_nights?: number;
  inclusions_cost_bottom?: boolean;
  terms_conditions?: string;
  allow_breakdown?: boolean;
  calculation_mode?: string;
  calculation_mode_phase_3?: string;
  phase_3_min_night?: number;
  phase_3_max_night?: number;
  label_by?: string;
  weekly?: boolean;
  thumbnail?: string;
  destination_profile_url?: string;
  master_info?: {
    check_in: string;
    check_out: string;
    booking?: string;
    [key: string]: any;
  };
  type?: string;
  booking?: string;
  currency?: string;
  guest_only?: boolean;
  guest_fee?: number;
  hot_deals_key?: string;

  // Tier settings (Phase-1 / Phase-2)
  tiers_night?: number;
  tiers_occp?: number;
  min_tiers_night?: number;

  // Price add (role-based surcharge)
  price_add?: {
    roles: any[];
    percent?: number;
    value?: number;
  };

  // Overrides for specific roles
  override?: Array<{
    roles: any[];
    settings: Record<string, any>;
  }>;

  [key: string]: any;
}

interface UnitsHotDealRequest {
  arrival: string;
  departure: string;
  resort_code: string;
  hot_deal_id: string;
  adults?: number;
  children?: number;
  children_ages?: number[];
}

interface UnitsOptionsHotDealRequest {
  arrival: string;
  departure: string;
  resort_code: string;
  resort_name?: string;
  hot_deal_id: string;
  adults: number;
  children: number;
  children_ages?: number[];
  units: { unit_code: string; qty: number }[];
  include_record?: boolean;
}

interface PricingRecord {
  max: number;
  checkin_day: number;          // 0=Sun … 6=Sat
  checkin_timestamp: number;    // unix seconds
  checkout_timestamp: number;   // unix seconds
  total_nights: number;
  nights?: number;
  price?: number;               // base BAR price (for discount modes)
  messages_offer: string[];
}

// ═══════════════════════════════════════════════════════════════════════════════
//  Hot-Deals catalog (lazy-loaded singleton from JSON file)
// ═══════════════════════════════════════════════════════════════════════════════

let hotDealsCache: Record<string, HotDealOffer> | null = null;

function loadHotDeals(): Record<string, HotDealOffer> {
  if (hotDealsCache) return hotDealsCache;

  const hotDealsPath = process.env.HOT_DEALS_PATH
    ? resolve(process.env.HOT_DEALS_PATH)
    : resolve(process.cwd(), "hot-deals.json");

  try {
    const raw = readFileSync(hotDealsPath, "utf-8");
    hotDealsCache = JSON.parse(raw);
    return hotDealsCache!;
  } catch (err) {
    console.error("[AvailabilityController] Failed to load hot-deals.json:", err);
    return {};
  }
}

/** Force-reload the hot deals cache. Call after an admin edits the JSON file. */
export function reloadHotDeals(): void {
  hotDealsCache = null;
  loadHotDeals();
}

// ═══════════════════════════════════════════════════════════════════════════════
//  Date helpers
// ═══════════════════════════════════════════════════════════════════════════════

function daysBetween(d1: string, d2: string): number {
  const ms1 = new Date(d1 + "T00:00:00Z").getTime();
  const ms2 = new Date(d2 + "T00:00:00Z").getTime();
  return Math.round((ms2 - ms1) / (86400_000));
}

/** 0=Sun … 6=Sat – same semantics as PHP date('w') */
function getDayOfWeek(dateStr: string): number {
  return new Date(dateStr + "T00:00:00Z").getUTCDay();
}

function isValidDate(dateStr: string): boolean {
  if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) return false;
  const d = new Date(dateStr + "T00:00:00Z");
  return !isNaN(d.getTime());
}

function toTimestamp(dateStr: string): number {
  return Math.floor(new Date(dateStr + "T00:00:00Z").getTime() / 1000);
}

// ═══════════════════════════════════════════════════════════════════════════════
//  Role matcher
//  Ported from PHP: booking_engine_current_user_roles_access()
//  Accepts a roles requirement array such as:
//    ["type_krr", "contract_kc_elite", {"and":["type_point","contract_n_discovery"],"negation":false}]
//  and a user roles set (a Set<string> of capabilities the member has).
// ═══════════════════════════════════════════════════════════════════════════════

function rolesAccessMatch(
  rolesRequirement: any[],
  userRoles: Set<string>,
): boolean {
  if (!Array.isArray(rolesRequirement)) return false;

  for (const mustRole of rolesRequirement) {
    let access = false;

    if (typeof mustRole === "object" && mustRole !== null && !Array.isArray(mustRole)) {
      // Compound rule: { and: [...], negation: false } or { or: [...] }
      if (Array.isArray(mustRole.and)) {
        access = true;
        for (const r of mustRole.and) {
          if (!userRoles.has(r)) {
            access = false;
            break;
          }
        }
      }
      if (Array.isArray(mustRole.or)) {
        access = false;
        for (const r of mustRole.or) {
          if (userRoles.has(r)) {
            access = true;
            break;
          }
        }
      }
      if (mustRole.negation === true) {
        access = !access;
      }
    } else if (typeof mustRole === "string") {
      access = userRoles.has(mustRole);
    }

    if (access) return true;
  }

  return false;
}

// ═══════════════════════════════════════════════════════════════════════════════
//  Offer eligibility & filtering
//  Ported from PHP: booking_engine_get_offers()
// ═══════════════════════════════════════════════════════════════════════════════

function filterActiveOffers(
  allOffers: Record<string, HotDealOffer>,
  userRoles: Set<string>,
): Record<string, HotDealOffer> {
  const now = Date.now();
  const result: Record<string, HotDealOffer> = {};

  for (const [key, raw] of Object.entries(allOffers)) {
    const value = { ...raw };

    // Defaults
    if (!value.type) value.type = "hot_deal";
    if (!value.currency) value.currency = "US$";
    if (!value.booking && value.master_info?.booking) {
      value.booking = value.master_info.booking;
    }

    // Validate starts / expires window
    const startsTs = value.starts ? new Date(value.starts).getTime() : 0;
    const expiresTs = value.expires ? new Date(value.expires).getTime() : 0;
    const startsOk = startsTs ? now >= startsTs : true;
    const expiresOk = expiresTs ? now <= expiresTs : true;

    if (!startsOk || !expiresOk) continue;

    // Check role access
    if (value.roles && Array.isArray(value.roles)) {
      if (!rolesAccessMatch(value.roles, userRoles)) continue;
    }

    // Apply role-based overrides
    if (value.override && Array.isArray(value.override)) {
      for (const ov of value.override) {
        if (rolesAccessMatch(ov.roles, userRoles)) {
          Object.assign(value, ov.settings);
          break;
        }
      }
    }

    // Defaults
    if (value.guest_only === undefined) value.guest_only = false;
    if (value.guest_fee === undefined) value.guest_fee = 0;
    value.hot_deals_key = key;

    result[key] = value;
  }

  return result;
}

function findOfferForResort(
  activeOffers: Record<string, HotDealOffer>,
  resortName: string,
  hotDealId: string,
): HotDealOffer | null {
  const offer = activeOffers[hotDealId];
  if (!offer) return null;
  if (offer.resort !== resortName) return null;
  return offer;
}

// ═══════════════════════════════════════════════════════════════════════════════
//  Calculation Engines
//  Ported line-by-line from PHP booking_engine.php
// ═══════════════════════════════════════════════════════════════════════════════

// ─── Phase-1 ─────────────────────────────────────────────────────────────────
// booking_engine_calc_offers_price_phase_1($offer, $night, $Record, $checkin)
function calcPhase1(
  offer: HotDealOffer,
  night: number,
  record: PricingRecord,
  checkin: string,
): number {
  const max = record.max;
  const price = offer.price ?? 0;
  const tiersNight = offer.tiers_night ?? 0;
  const tiersOccp = offer.tiers_occp ?? 0;
  const minTiersNight = offer.min_tiers_night ?? 3;
  let newPrice = 0;

  // If price is a rules-based array and checkin is provided
  if (typeof price === "object" && price !== null && checkin) {
    const priceRules = { ...(price as any) };
    const priceDefault = priceRules.default ?? null;
    delete priceRules.default;

    const checkinTime = toTimestamp(checkin);
    const checkoutTime = toTimestamp(checkin) + (night - 1) * 86400;
    let nightLeft = night;

    for (const [, pricing] of Object.entries(priceRules)) {
      if (typeof pricing !== "object" || !(pricing as any).from) continue;
      const p = pricing as any;
      const weekly = nightLeft === 7;
      const pricingFrom = toTimestamp(p.from);
      const pricingTo = toTimestamp(p.to);

      if (pricingFrom <= checkinTime) {
        if (checkoutTime <= pricingTo) {
          newPrice = weekly ? p.weekly : p.daily * nightLeft;
          nightLeft = 0;
          break;
        } else if (checkinTime <= pricingTo) {
          // Spans multiple pricing periods — check each day
          let currentTime = checkinTime;
          const nightCheck = nightLeft;
          for (let i = 0; i < nightCheck; i++) {
            if (pricingFrom <= currentTime && currentTime <= pricingTo) {
              newPrice += p.daily;
              nightLeft--;
              currentTime += 86400;
            } else {
              break;
            }
          }
        }
      }
    }

    // Check for default pricing if nights remain
    if (nightLeft > 0 && priceDefault) {
      if (nightLeft === 7) {
        newPrice += priceDefault.weekly;
        nightLeft = 0;
      } else if (priceDefault.day) {
        const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
        let currentDate = new Date(checkin + "T00:00:00Z");
        const nightCheck = nightLeft;
        for (let i = 0; i < nightCheck; i++) {
          const dayName = dayNames[currentDate.getUTCDay()];
          if (priceDefault.day[dayName] !== undefined) {
            newPrice += priceDefault.day[dayName];
          }
          nightLeft--;
          currentDate = new Date(currentDate.getTime() + 86400_000);
        }
      }
    }
  }

  if (!newPrice) {
    // Tiers Night
    if (tiersNight && night !== offer.night) {
      if (night < minTiersNight) return 0;

      if ((offer.night - night) % tiersNight === 0) {
        const nightReducePercent = ((offer.night - night) / tiersNight) * 0.1;
        newPrice = Math.round((price as number) - (price as number) * nightReducePercent);
      }
    } else {
      newPrice = night === offer.night ? (price as number) : 0;
    }
  }

  // Tiers Occupancy
  if (newPrice && max !== (offer.occp ?? 0)) {
    if (tiersOccp) {
      if ((max - (offer.occp ?? 0)) % tiersOccp === 0 && max >= (offer.occp ?? 0)) {
        const occpIncreasePercent = ((max - (offer.occp ?? 0)) / tiersOccp) * 0.5;
        newPrice = Math.round(newPrice + newPrice * occpIncreasePercent);
      }
    } else {
      newPrice = 0;
    }
  }

  return newPrice;
}

// ─── Phase-2 ─────────────────────────────────────────────────────────────────
// booking_engine_calc_offers_price_phase_2($offer, $night, $max)
function calcPhase2(
  offer: HotDealOffer,
  night: number,
  max: number,
): number {
  if (offer.night !== 7) return 0;

  const tiersOccp = offer.tiers_occp ?? 0;
  const minTiersNight = offer.min_tiers_night ?? 3;
  let newPrice = 0;

  if (tiersOccp) {
    if ((max - (offer.occp ?? 0)) % tiersOccp === 0 && max >= (offer.occp ?? 0)) {
      newPrice = (offer.price ?? 0) + ((max - (offer.occp ?? 0)) / tiersOccp) * 100;
    }
  } else {
    newPrice = max === (offer.occp ?? 0) ? (offer.price ?? 0) : 0;
  }

  if (night < 5) {
    if (night >= minTiersNight) {
      newPrice = Math.round(newPrice * 0.2 * night);
    } else {
      return 0;
    }
  }

  return newPrice;
}

// ─── Phase-3 ─────────────────────────────────────────────────────────────────
// booking_engine_calc_offers_price_phase_3($offer, $night, $Record)
function calcPhase3(
  offer: HotDealOffer,
  night: number,
  record: PricingRecord,
): number {
  const max = record.max;
  const minNight = offer.phase_3_min_night ?? 4;

  let offerMaxNight = 7;
  if (offer.weekly !== undefined) {
    if (!offer.weekly) offerMaxNight = 21;
  }
  if (offer.phase_3_max_night !== undefined) {
    offerMaxNight = offer.phase_3_max_night;
  }

  if (night < minNight || night > offerMaxNight) {
    if (offer.booking) record.messages_offer.push(offer.booking);
    return 0;
  }
  if (record.total_nights < minNight || record.total_nights > offerMaxNight) {
    if (offer.booking) record.messages_offer.push(offer.booking);
    return 0;
  }

  let newPrice = 0;
  let directPrice = false;
  const occpPrice = offer.occp_price;

  if (occpPrice && occpPrice[String(max)] !== undefined) {
    const selectedPrice = occpPrice[String(max)];

    if (typeof selectedPrice === "object" && selectedPrice !== null && !Array.isArray(selectedPrice)) {
      if (selectedPrice[String(night)] !== undefined) {
        const nightEntry = selectedPrice[String(night)];

        if (typeof nightEntry === "object" && nightEntry !== null) {
          // By checkin_days
          if (
            nightEntry.checkin_days &&
            nightEntry.checkin_days[String(record.checkin_day)] !== undefined
          ) {
            const checkinDayPrice = nightEntry.checkin_days[String(record.checkin_day)];

            if (typeof checkinDayPrice === "object" && checkinDayPrice !== null) {
              // By travel_date_price
              if (Array.isArray(checkinDayPrice.travel_date_price)) {
                for (const tdp of checkinDayPrice.travel_date_price) {
                  const tdpFrom = toTimestamp(tdp.from);
                  const tdpUntil = toTimestamp(tdp.until);
                  if (tdpFrom <= record.checkin_timestamp && record.checkout_timestamp <= tdpUntil) {
                    newPrice = tdp.price;
                    directPrice = true;
                  }
                }
              }
            } else {
              newPrice = Number(checkinDayPrice);
              directPrice = true;
            }
          }
        } else {
          // nightEntry is a direct price number
          newPrice = Number(nightEntry);
          directPrice = true;
        }
      } else if (!offer.weekly && selectedPrice["1"] !== undefined) {
        // Non-weekly: multiply per-night price × night
        newPrice = night * Number(selectedPrice["1"]);
        directPrice = true;
      }
    } else if (night === offer.night) {
      // selectedPrice is a flat number
      newPrice = Number(selectedPrice);
      directPrice = true;
    }
  } else if (offer.occp !== undefined && offer.price !== undefined) {
    if (max === offer.occp) {
      newPrice = offer.price;
      directPrice = true;
    }
  }

  if (!newPrice && offer.booking) {
    record.messages_offer.push(offer.booking);
  }

  return newPrice;
}

// ─── Phase-3 Discount ────────────────────────────────────────────────────────
// booking_engine_calc_offers_price_discount($offer, $night, $Record)
// The "discount" value from occp_price is a percentage off the BAR price.
function calcDiscount(
  offer: HotDealOffer,
  night: number,
  record: PricingRecord,
): number {
  const minTiersNight = offer.phase_3_min_night ?? 3;
  const totalNights = record.total_nights || night;

  if (totalNights < minTiersNight) return 0;

  let discount = 0;

  if (offer.price !== undefined && typeof offer.price === "number") {
    discount = offer.price;
  } else if (offer.occp_price) {
    const max = record.max;
    if (offer.occp_price[String(max)] !== undefined) {
      const selectedPrice = offer.occp_price[String(max)];
      if (typeof selectedPrice === "object" && selectedPrice[String(totalNights)] !== undefined) {
        const nightEntry = selectedPrice[String(totalNights)];
        if (typeof nightEntry === "object" && nightEntry !== null) {
          // By checkin_days
          if (
            nightEntry.checkin_days &&
            nightEntry.checkin_days[String(record.checkin_day)] !== undefined
          ) {
            const val = nightEntry.checkin_days[String(record.checkin_day)];
            if (typeof val === "object" && val !== null && Array.isArray(val.travel_date_price)) {
              for (const tdp of val.travel_date_price) {
                const tdpFrom = toTimestamp(tdp.from);
                const tdpUntil = toTimestamp(tdp.until);
                if (tdpFrom <= record.checkin_timestamp && record.checkout_timestamp <= tdpUntil) {
                  discount = tdp.price;
                }
              }
            } else {
              discount = Number(val);
            }
          }
        } else {
          discount = Number(nightEntry);
        }
      }
    }
  }

  if (discount) {
    // discount is a percentage; apply to the BAR price
    const barPrice = record.price ?? 0;
    if (barPrice > 0) {
      return barPrice - (barPrice * discount) / 100;
    }
  }

  return 0;
}

// ─── Phase-3 Discount 25 BAR ─────────────────────────────────────────────────
// booking_engine_calc_offers_price_discount_25bar($offer, $night, $Record)
// Same as discount but with a 25% BAR uplift fallback.
function calcDiscount25Bar(
  offer: HotDealOffer,
  night: number,
  record: PricingRecord,
): number {
  const minTiersNight = offer.phase_3_min_night ?? 3;
  const totalNights = record.total_nights || night;

  if (totalNights < minTiersNight) return 0;

  let discount = 0;

  if (offer.price !== undefined && typeof offer.price === "number") {
    discount = offer.price;
  } else if (offer.occp_price) {
    const max = record.max;
    if (offer.occp_price[String(max)] !== undefined) {
      const selectedPrice = offer.occp_price[String(max)];
      if (typeof selectedPrice === "object" && selectedPrice[String(totalNights)] !== undefined) {
        const nightEntry = selectedPrice[String(totalNights)];
        if (typeof nightEntry === "object" && nightEntry !== null) {
          if (
            nightEntry.checkin_days &&
            nightEntry.checkin_days[String(record.checkin_day)] !== undefined
          ) {
            const val = nightEntry.checkin_days[String(record.checkin_day)];
            if (typeof val === "object" && val !== null) {
              // gl_discount mode with per-day discount — requires availability data
              // which is not available in the console context. Fall through.
              if (val.gl_discount) {
                // Cannot fully calculate without resort availability data.
                // Return 0 so it falls back to the simple discount path.
                discount = 0;
              } else if (Array.isArray(val.travel_date_price)) {
                for (const tdp of val.travel_date_price) {
                  const tdpFrom = toTimestamp(tdp.from);
                  const tdpUntil = toTimestamp(tdp.until);
                  if (tdpFrom <= record.checkin_timestamp && record.checkout_timestamp <= tdpUntil) {
                    discount = tdp.price;
                  }
                }
              }
            } else {
              discount = Number(val);
            }
          }
        } else {
          discount = Number(nightEntry);
        }
      }
    }
  }

  if (discount) {
    const barPrice = record.price ?? 0;
    if (barPrice > 0) {
      // 25% BAR uplift: price = BAR * (100/75), then apply discount
      const uplifted = barPrice * (100 / 75);
      return uplifted - (uplifted * discount) / 100;
    }
  }

  return 0;
}

// ═══════════════════════════════════════════════════════════════════════════════
//  Main pricing dispatcher
//  Ported from PHP: booking_engine_calc_offers_price()
// ═══════════════════════════════════════════════════════════════════════════════

function calcOfferPrice(
  offer: HotDealOffer,
  night: number,
  record: PricingRecord,
  checkin: string,
  userRoles: Set<string>,
): number {
  const max = record.max;
  let price = 0;

  const mode = offer.calculation_mode ?? "";

  if (mode === "phase-2") {
    price = calcPhase2(offer, night, max);
  } else if (mode === "phase-3") {
    const sub = offer.calculation_mode_phase_3 ?? "";
    if (sub === "discount") {
      price = calcDiscount(offer, night, record);
    } else if (sub === "discount_bar_25") {
      price = calcDiscount25Bar(offer, night, record);
    } else {
      price = calcPhase3(offer, night, record);
    }
  } else {
    // Default: phase-1
    price = calcPhase1(offer, night, record, checkin);
  }

  // price_add: role-based surcharge
  if (price && offer.price_add && typeof offer.price_add === "object") {
    if (rolesAccessMatch(offer.price_add.roles, userRoles)) {
      if (offer.price_add.percent !== undefined) {
        price += price * (offer.price_add.percent / 100);
      } else if (offer.price_add.value !== undefined) {
        price += offer.price_add.value;
      }
      price = Math.round(price);
    }
  }

  return price;
}

// ═══════════════════════════════════════════════════════════════════════════════
//  Controller
// ═══════════════════════════════════════════════════════════════════════════════

export function AvailabilityController(c: Context) {
  const db = c.get("datastore");
  const memberDb = c.get("memberDatastore");

  /**
   * Build a Set of user role strings for the current member.
   * In the PHP codebase, WordPress capabilities like "type_krr", "contract_kc_elite"
   * are checked via current_user_can(). Here we query member_access_role_policies
   * from the membership (Updot) database and build an equivalent set.
   *
   * If no session member context exists (admin console use), return a
   * permissive set that matches the most common roles.
   */
  async function resolveUserRoles(): Promise<Set<string>> {
    // In admin console context we have no individual member session.
    // Return a universal role set so all offers are visible to admins.
    // When this API is consumed by member-facing clients, this should
    // query the memberDb for the member's actual access roles.
    const universalRoles = new Set<string>([
      "type_krr",
      "type_point",
      "type_point_xs",
      "type_hok",
      "contract_kc_elite",
      "contract_kc_elite_non",
      "contract_n_discovery",
      "contract_kcde_nindia",
      "contract_kcd_india",
      "status_active",
    ]);
    return universalRoles;
  }

  return {
    /**
     * POST /v1/availability/units-hot-deal
     *
     * Calculate hot deal pricing for units at a resort.
     * Mirrors: AvailabilityUnitsHotDealController::bea_availability_units_hot_deal
     */
    async UnitsHotDeal() {
      const body = (await c.req.json()) as UnitsHotDealRequest;
      const errors: Record<string, string> = {};

      // ── Validate params ──
      if (!body.arrival) {
        errors.arrival = 'The "arrival" field is required!';
      } else if (!isValidDate(body.arrival)) {
        errors.arrival = "Invalid date format! Expected YYYY-MM-DD.";
      }

      if (!body.departure) {
        errors.departure = 'The "departure" field is required!';
      } else if (!isValidDate(body.departure)) {
        errors.departure = "Invalid date format! Expected YYYY-MM-DD.";
      }

      if (!errors.arrival && !errors.departure) {
        if (body.arrival >= body.departure) {
          errors.arrival = 'Invalid travel date, "arrival" date should be prior to "departure" date!';
        }
      }

      if (!body.resort_code) {
        errors.resort_code = 'The "resort_code" field is required!';
      }

      if (!body.hot_deal_id) {
        errors.hot_deal_id = 'The "hot_deal_id" field is required!';
      }

      // ── Resolve resort from code ──
      let resortName: string | null = null;
      let resortId: string | null = null;
      if (!errors.resort_code) {
        const resort = await db
          .selectFrom("resorts")
          .where("code", "=", body.resort_code)
          .select(["id", "name"])
          .executeTakeFirst();

        if (!resort) {
          errors.resort_code = 'The "resort_code" not found in database!';
        } else {
          resortName = resort.name;
          resortId = resort.id;
        }
      }

      // ── Load & filter hot deals for current user roles ──
      const userRoles = await resolveUserRoles();
      const allOffers = loadHotDeals();
      const activeOffers = filterActiveOffers(allOffers, userRoles);

      let hotDealData: HotDealOffer | null = null;
      if (!errors.hot_deal_id && resortName) {
        hotDealData = findOfferForResort(activeOffers, resortName, body.hot_deal_id);
        if (!hotDealData) {
          // Try without resort filter (name mismatch between Strapi and JSON)
          hotDealData = activeOffers[body.hot_deal_id] ?? null;
        }
        if (!hotDealData) {
          errors.hot_deal_id = 'The "hot_deal_id" not found or not active for your roles!';
        }
      }

      // ── Validate travel dates against offer's master_info range ──
      if (hotDealData?.master_info && !errors.arrival && !errors.departure) {
        const mi = hotDealData.master_info;
        if (mi.check_in && (body.arrival < mi.check_in || body.arrival >= mi.check_out)) {
          errors.arrival = "The date must be within the offer's travel dates!";
        }
        if (mi.check_out && (body.departure > mi.check_out || body.departure < mi.check_in)) {
          errors.departure = "The date must be within the offer's travel dates!";
        }
      }

      // ── Validate adults ──
      if (body.adults !== undefined) {
        if (!Number.isFinite(body.adults) || body.adults < 1) {
          errors.adults = 'Number of "adults" should be at least 1!';
        }
      }

      if (Object.keys(errors).length > 0) {
        return c.json(
          { success: false, message: "One or more fields have an error. " + Object.values(errors).join(" "), errors },
          400,
        );
      }

      // ── Calculate pricing ──
      const nights = daysBetween(body.arrival, body.departure);
      const checkinDay = getDayOfWeek(body.arrival);
      const checkinTs = toTimestamp(body.arrival);
      const checkoutTs = toTimestamp(body.departure);

      // Get units from database
      const units = resortId
        ? await db
            .selectFrom("resort_units")
            .where("resort_id", "=", resortId)
            .where("is_active", "=", true)
            .where("eligible_for_hot_deals", "=", true)
            .selectAll()
            .execute()
        : [];

      // Filter units by the hot deal's unit_code list
      const eligibleUnitCodes = hotDealData?.unit_code ?? [];
      const filteredUnits = eligibleUnitCodes.length > 0
        ? units.filter((u) => eligibleUnitCodes.includes(u.unit_vp_code))
        : units;

      // Build pricing results per unit
      const unitResults = filteredUnits.map((unit) => {
        const maxOccp = (unit.total_occupancy ?? 2);
        const maxAdults = unit.adult_occupancy ?? unit.total_occupancy ?? 2;
        const maxChildren = unit.children_occupancy ?? 0;

        const record: PricingRecord = {
          max: maxOccp,
          checkin_day: checkinDay,
          checkin_timestamp: checkinTs,
          checkout_timestamp: checkoutTs,
          total_nights: nights,
          nights: nights,
          messages_offer: [],
        };

        const price = hotDealData
          ? calcOfferPrice(hotDealData, nights, record, body.arrival, userRoles)
          : 0;

        return {
          unit_code: unit.unit_vp_code,
          unit_name: unit.unit_name,
          unit_type: unit.unit_type,
          max_occp: maxOccp + maxChildren,
          max_adults: maxAdults,
          max_children: maxChildren,
          price,
          currency: hotDealData?.currency ?? "US$",
          nights,
          checkin_day: checkinDay,
          messages: record.messages_offer,
          direct: price > 0,
          featured_image: unit.featured_image,
          description: unit.description,
        };
      });

      const result = {
        [body.resort_code]: {
          units: unitResults,
          offer: hotDealData
            ? {
                id: body.hot_deal_id,
                resort: hotDealData.resort,
                night: hotDealData.night,
                calculation_mode: hotDealData.calculation_mode,
                calculation_mode_phase_3: hotDealData.calculation_mode_phase_3,
                weekly: hotDealData.weekly ?? true,
                currency: hotDealData.currency ?? "US$",
                thumbnail: hotDealData.thumbnail,
                destination_profile_url: hotDealData.destination_profile_url,
                terms_conditions: hotDealData.terms_conditions,
                inclusions: hotDealData.inclusions,
                travel_between: hotDealData.travel_between,
                label_by: hotDealData.label_by,
                phase_3_min_night: hotDealData.phase_3_min_night,
                phase_3_max_night: hotDealData.phase_3_max_night,
                occ_up_to: hotDealData.occ_up_to,
                allow_breakdown: hotDealData.allow_breakdown,
                master_info: hotDealData.master_info,
              }
            : null,
        },
      };

      return c.json(result, 200);
    },

    /**
     * POST /v1/availability/units/options-hot-deal
     *
     * Calculate booking options for selected units.
     * Mirrors: AvailabilityUnitsOptionsHotDealController
     */
    async UnitsOptionsHotDeal() {
      const body = (await c.req.json()) as UnitsOptionsHotDealRequest;
      const errors: Record<string, string> = {};

      // ── Validate params ──
      if (!body.arrival) {
        errors.arrival = 'The "arrival" field is required!';
      } else if (!isValidDate(body.arrival)) {
        errors.arrival = "Invalid date format! Expected YYYY-MM-DD.";
      }

      if (!body.departure) {
        errors.departure = 'The "departure" field is required!';
      } else if (!isValidDate(body.departure)) {
        errors.departure = "Invalid date format! Expected YYYY-MM-DD.";
      }

      if (!errors.arrival && !errors.departure) {
        if (body.arrival >= body.departure) {
          errors.arrival = 'Invalid travel date, "arrival" date should be prior to "departure" date!';
        }
      }

      if (!body.resort_code) {
        errors.resort_code = 'The "resort_code" field is required!';
      }

      if (!body.hot_deal_id) {
        errors.hot_deal_id = 'The "hot_deal_id" field is required!';
      }

      if (!body.units || !Array.isArray(body.units) || body.units.length === 0) {
        errors.units = 'The "units" field must be a non-empty array!';
      }

      if (!Number.isFinite(body.adults) || body.adults < 1) {
        errors.adults = 'Number of "adults" should be at least 1!';
      }

      // Children defaults
      const children = body.children ?? 0;
      let childrenAges = body.children_ages ?? [];
      if (children > 0 && childrenAges.length < children) {
        // Default ages to 10 if not provided (mirrors PHP logic)
        childrenAges = Array.from({ length: children }, (_, i) => childrenAges[i] ?? 10);
      }

      // ── Resolve resort ──
      let resortName: string | null = body.resort_name ?? null;
      let resortId: string | null = null;
      if (!errors.resort_code) {
        const resort = await db
          .selectFrom("resorts")
          .where("code", "=", body.resort_code)
          .select(["id", "name"])
          .executeTakeFirst();
        if (resort) {
          resortName = resort.name;
          resortId = resort.id;
        }
      }

      // ── Load & filter hot deals ──
      const userRoles = await resolveUserRoles();
      const allOffers = loadHotDeals();
      const activeOffers = filterActiveOffers(allOffers, userRoles);

      let hotDealData: HotDealOffer | null = null;
      if (!errors.hot_deal_id && resortName) {
        hotDealData = findOfferForResort(activeOffers, resortName, body.hot_deal_id);
        if (!hotDealData) {
          hotDealData = activeOffers[body.hot_deal_id] ?? null;
        }
        if (!hotDealData) {
          errors.hot_deal_id = 'The "hot_deal_id" not found or not active!';
        }
      }

      if (Object.keys(errors).length > 0) {
        return c.json(
          { success: false, message: "One or more fields have an error. " + Object.values(errors).join(" "), errors },
          400,
        );
      }

      // ── Check hot_deals access via member_access_role_policies ──
      let allowBooking = true;
      let accessAlert = "";
      try {
        // Query all policies where hot_deals = 'BLOCKED'
        // In a full implementation, you would look up the current member's
        // club → status → policy chain and check if hot_deals !== 'ALLOWED'.
        // For admin console usage this always allows.
      } catch (err) {
        console.error("[AvailabilityController] Error checking access roles:", err);
      }

      // ── Calculate pricing per unit ──
      const nights = daysBetween(body.arrival, body.departure);
      const checkinDay = getDayOfWeek(body.arrival);
      const checkinTs = toTimestamp(body.arrival);
      const checkoutTs = toTimestamp(body.departure);

      // Lookup DB units for enrichment
      const dbUnits = resortId
        ? await db
            .selectFrom("resort_units")
            .where("resort_id", "=", resortId)
            .where("is_active", "=", true)
            .selectAll()
            .execute()
        : [];

      const dbUnitMap = new Map(dbUnits.map((u) => [u.unit_vp_code, u]));

      // Build unit info response
      const unitsResult = body.units.map((paramUnit) => {
        const dbUnit = dbUnitMap.get(paramUnit.unit_code);
        const maxOccp = dbUnit ? (dbUnit.total_occupancy ?? 2) : 2;
        const maxAdults = dbUnit?.adult_occupancy ?? dbUnit?.total_occupancy ?? 2;
        const maxChildren = dbUnit?.children_occupancy ?? 0;

        return {
          qty: paramUnit.qty,
          unit_name: dbUnit?.unit_name ?? paramUnit.unit_code,
          unit_code: paramUnit.unit_code,
          max_occp: maxOccp + maxChildren,
          max_adults: maxAdults,
          max_children: maxChildren,
        };
      });

      // Build booking options with pricing
      const bookingOptions = allowBooking
        ? body.units.map((paramUnit) => {
            const dbUnit = dbUnitMap.get(paramUnit.unit_code);
            const maxOccp = dbUnit
              ? (dbUnit.total_occupancy ?? 2)
              : body.adults + children;

            const record: PricingRecord = {
              max: maxOccp,
              checkin_day: checkinDay,
              checkin_timestamp: checkinTs,
              checkout_timestamp: checkoutTs,
              total_nights: nights,
              nights: nights,
              messages_offer: [],
            };

            const price = hotDealData
              ? calcOfferPrice(hotDealData, nights, record, body.arrival, userRoles)
              : 0;

            return {
              unit_code: paramUnit.unit_code,
              unit_name: dbUnit?.unit_name ?? paramUnit.unit_code,
              qty: paramUnit.qty,
              price,
              currency: hotDealData?.currency ?? "US$",
              nights,
              arrival: body.arrival,
              departure: body.departure,
              checkin_day: checkinDay,
              adults: body.adults,
              children,
              children_ages: childrenAges,
              messages: record.messages_offer,
              offer_id: body.hot_deal_id,
              calculation_mode: hotDealData?.calculation_mode,
            };
          })
        : [];

      const result = {
        resort_code: body.resort_code,
        resort_name: resortName,
        units: unitsResult,
        booking_options: bookingOptions,
        allow_booking: allowBooking,
        access_alert: accessAlert || undefined,
      };

      return c.json(result, 200);
    },
  };
}
