import { createHash } from "crypto";

const isValidUUID = (str: string): boolean => {
  return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(str);
};

const stringToUUID = (str: string): string => {
  const hash = createHash("sha256").update(str).digest("hex");
  return [
    hash.substring(0, 8),
    hash.substring(8, 12),
    "4" + hash.substring(13, 16),
    ((parseInt(hash.substring(16, 18), 16) & 0x3f) | 0x80).toString(16) + hash.substring(18, 20),
    hash.substring(20, 32),
  ].join("-");
};

const formatToLocalDate = (d: any): string => {
  if (!d) return "";
  const dateObj = d instanceof Date ? d : new Date(d);
  if (isNaN(dateObj.getTime())) return "";
  const yyyy = dateObj.getFullYear();
  const mm = String(dateObj.getMonth() + 1).padStart(2, "0");
  const dd = String(dateObj.getDate()).padStart(2, "0");
  return `${yyyy}-${mm}-${dd}`;
};

const findResortId = async (db: any, resortName: string): Promise<string | null> => {
  const dbResorts = await db.selectFrom("resorts" as any).select(["id", "name"]).execute();
  const clean = (s: string) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
  const cleanJsonName = clean(resortName);

  for (const r of dbResorts) {
    if (clean(r.name) === cleanJsonName) {
      return r.id;
    }
  }
  for (const r of dbResorts) {
    if (cleanJsonName.includes(clean(r.name)) || clean(r.name).includes(cleanJsonName)) {
      return r.id;
    }
  }
  return null;
};

const findResortUnitId = async (db: any, resortId: string, unitCode: string): Promise<string | null> => {
  if (!unitCode) return null;
  const unit = await db
    .selectFrom("resort_units" as any)
    .select(["id", "unit_vp_code"])
    .where("resort_id", "=", resortId)
    .execute();
  const match = unit.find(
    (u: any) => u.unit_vp_code?.toLowerCase().trim() === unitCode.toLowerCase().trim(),
  );
  return match?.id ?? null;
};

// Mirrors the "relational shell + raw_config" approach used for Hot Deals:
// resort/unit_code/dates/nights live in real columns for filtering/joins,
// everything else from the source JSON (roles, travel_between, unit_points,
// override, terms_conditions, booking_caption, expose_option,
// with_checkin_day, hot_deal_type, more_rules, ...) round-trips via
// raw_config so nothing entered in the admin UI is ever dropped.
const formatDbRuleToTravelRule = (tr: any): Record<string, any> => {
  const computed: Record<string, any> = {
    id: tr.id,
    resort: tr.resort_name,
    unit_code: tr.unit_code,
    alias_target_unit_code: tr.alias_target_unit_code || undefined,
    rule_id: tr.rule_id || undefined,
    min_nights: tr.min_nights ?? undefined,
    max_nights: tr.max_nights ?? undefined,
    max_occ: tr.max_occ ?? undefined,
    confirm_from: tr.confirm_from ? formatToLocalDate(tr.confirm_from) : "",
    confirm_to: tr.confirm_to ? formatToLocalDate(tr.confirm_to) : "",
    is_active: tr.is_active,
    publish_mode: tr.publish_mode,
    sort_order: tr.sort_order ?? 0,
  };

  const raw = tr.raw_config && typeof tr.raw_config === "object" ? tr.raw_config : {};
  return { ...computed, ...raw, id: tr.id };
};

export class TravelRulesService {
  private static instance: TravelRulesService;

  static getInstance(): TravelRulesService {
    if (!TravelRulesService.instance) {
      TravelRulesService.instance = new TravelRulesService();
    }
    return TravelRulesService.instance;
  }

  async listRules(
    db: any,
    filters: { resort?: string; unit_code?: string; search?: string; page?: number; limit?: number } = {},
  ) {
    const page = filters.page && filters.page > 0 ? filters.page : 1;
    const limit = filters.limit && filters.limit > 0 ? Math.min(filters.limit, 200) : 50;
    const offset = (page - 1) * limit;

    let query = db
      .selectFrom("travel_rules as tr" as any)
      .leftJoin("resorts as r" as any, "r.id" as any, "tr.resort_id" as any)
      .select([
        "tr.id" as any,
        "tr.resort_id" as any,
        "tr.resort_unit_id" as any,
        "tr.unit_code" as any,
        "tr.alias_target_unit_code" as any,
        "tr.rule_id" as any,
        "tr.min_nights" as any,
        "tr.max_nights" as any,
        "tr.max_occ" as any,
        "tr.confirm_from" as any,
        "tr.confirm_to" as any,
        "tr.is_active" as any,
        "tr.publish_mode" as any,
        "tr.sort_order" as any,
        "tr.raw_config" as any,
        "r.name as resort_name" as any,
      ])
      .orderBy("r.name", "asc")
      .orderBy("tr.unit_code", "asc")
      .orderBy("tr.sort_order", "asc");

    let countQuery = db.selectFrom("travel_rules as tr" as any).select((eb: any) => eb.fn.count("tr.id").as("count"));

    if (filters.resort) {
      query = query.where("r.name", "ilike", `%${filters.resort}%`);
      countQuery = countQuery
        .leftJoin("resorts as r" as any, "r.id" as any, "tr.resort_id" as any)
        .where("r.name", "ilike", `%${filters.resort}%`);
    }
    if (filters.unit_code) {
      query = query.where("tr.unit_code", "ilike", `%${filters.unit_code}%`);
      countQuery = countQuery.where("tr.unit_code", "ilike", `%${filters.unit_code}%`);
    }
    if (filters.search) {
      query = query.where((eb: any) =>
        eb.or([
          eb("r.name", "ilike", `%${filters.search}%`),
          eb("tr.unit_code", "ilike", `%${filters.search}%`),
          eb("tr.rule_id", "ilike", `%${filters.search}%`),
        ]),
      );
    }

    const rows = await query.limit(limit).offset(offset).execute();
    const countResult = await countQuery.executeTakeFirst();
    const total = Number(countResult?.count ?? 0);

    return {
      success: true,
      data: { items: rows.map(formatDbRuleToTravelRule), total, page, limit },
      message: "OK",
    };
  }

  async getRule(db: any, id: string) {
    const parsedId = isValidUUID(id) ? id : stringToUUID(id);

    const tr = await db
      .selectFrom("travel_rules as tr" as any)
      .leftJoin("resorts as r" as any, "r.id" as any, "tr.resort_id" as any)
      .selectAll("tr" as any)
      .select(["r.name as resort_name" as any])
      .where("tr.id" as any, "=", parsedId)
      .executeTakeFirst();

    if (!tr) {
      return { success: false, data: null, message: "Travel rule not found" };
    }

    return { success: true, data: formatDbRuleToTravelRule(tr), message: "OK" };
  }

  async createRule(db: any, id: string, rule: Record<string, any>) {
    const parsedId = isValidUUID(id) ? id : stringToUUID(id);

    const existing = await db
      .selectFrom("travel_rules" as any)
      .select("id")
      .where("id", "=", parsedId)
      .executeTakeFirst();
    if (existing) {
      return { success: false, data: null, message: "Rule ID already exists" };
    }

    if (!rule.unit_code) {
      return { success: false, data: null, message: "unit_code is required" };
    }

    const resortId = rule.resort ? await findResortId(db, rule.resort) : null;
    if (rule.resort && !resortId) {
      return { success: false, data: null, message: `No matching resort found for "${rule.resort}"` };
    }
    const resortUnitId = resortId ? await findResortUnitId(db, resortId, rule.unit_code) : null;

    const ruleData = {
      id: parsedId,
      resort_id: resortId,
      resort_unit_id: resortUnitId,
      unit_code: rule.unit_code,
      alias_target_unit_code: rule.alias_target_unit_code || null,
      rule_id: rule.rule_id || null,
      min_nights: rule.min_nights ?? null,
      max_nights: rule.max_nights ?? null,
      max_occ: rule.max_occ ?? null,
      confirm_from: rule.confirm_from ? new Date(rule.confirm_from) : null,
      confirm_to: rule.confirm_to ? new Date(rule.confirm_to) : null,
      is_active: rule.is_active ?? true,
      publish_mode: rule.publish_mode ?? "DRAFT",
      sort_order: rule.sort_order ?? 0,
      raw_config: rule,
      updated_at: new Date(),
    };

    await db.insertInto("travel_rules" as any).values(ruleData).execute();

    return { success: true, data: { id: parsedId, ...rule }, message: "Rule created" };
  }

  async updateRule(db: any, id: string, rule: Record<string, any>) {
    const parsedId = isValidUUID(id) ? id : stringToUUID(id);

    const existing = await db
      .selectFrom("travel_rules" as any)
      .select("id")
      .where("id", "=", parsedId)
      .executeTakeFirst();
    if (!existing) {
      return { success: false, data: null, message: "Travel rule not found" };
    }

    const resortId = rule.resort ? await findResortId(db, rule.resort) : null;
    if (rule.resort && !resortId) {
      return { success: false, data: null, message: `No matching resort found for "${rule.resort}"` };
    }
    const resortUnitId = resortId && rule.unit_code ? await findResortUnitId(db, resortId, rule.unit_code) : null;

    const ruleData = {
      resort_id: resortId,
      resort_unit_id: resortUnitId,
      unit_code: rule.unit_code,
      alias_target_unit_code: rule.alias_target_unit_code || null,
      rule_id: rule.rule_id || null,
      min_nights: rule.min_nights ?? null,
      max_nights: rule.max_nights ?? null,
      max_occ: rule.max_occ ?? null,
      confirm_from: rule.confirm_from ? new Date(rule.confirm_from) : null,
      confirm_to: rule.confirm_to ? new Date(rule.confirm_to) : null,
      is_active: rule.is_active ?? true,
      publish_mode: rule.publish_mode ?? "DRAFT",
      sort_order: rule.sort_order ?? 0,
      raw_config: rule,
      updated_at: new Date(),
    };

    await db.updateTable("travel_rules" as any).set(ruleData).where("id", "=", parsedId).execute();

    return { success: true, data: { id: parsedId, ...rule }, message: "Rule updated" };
  }

  async deleteRule(db: any, id: string) {
    const parsedId = isValidUUID(id) ? id : stringToUUID(id);

    const existing = await db
      .selectFrom("travel_rules" as any)
      .select("id")
      .where("id", "=", parsedId)
      .executeTakeFirst();
    if (!existing) {
      return { success: false, data: null, message: "Travel rule not found" };
    }

    await db.deleteFrom("travel_rules" as any).where("id", "=", parsedId).execute();
    return { success: true, data: null, message: "Rule deleted" };
  }
}
