/**
 * Itinerary audit summary helpers.
 *
 * Replaces the old single-line "Updated itinerary details" placeholder
 * with concrete per-row diffs so an ops manager scanning the audit
 * timeline can see EXACTLY what was added or removed:
 *   "transport_items: +Dubai → Abu Dhabi 2026-04-25 09:00 (PVT, SUV)
 *                    -Dubai → Abu Dhabi 2026-04-25 14:00 (SIC)"
 *
 * Save paths are delete-then-reinsert today, so we can't do a true
 * row-by-row UPDATE diff. Instead we snapshot BEFORE rows from the DB,
 * then compare against the new rows by a content-based key. Anything
 * present only in BEFORE is a removal; only in AFTER is an addition.
 */

import type { AuditChange } from './audit-types';

type Lookup = Map<string, string>;

export interface ItineraryAuditContext {
  hotelNames?: Lookup; // hotel_id → name
  transportLabels?: Lookup; // transport_service_id → "from → to"
  activityNames?: Lookup; // activity_id → name
  ticketNames?: Lookup; // ticket_id → name
  vehicleTypeNames?: Lookup; // vehicle_type_id → name
}

function get(map: Lookup | undefined, id: string | null | undefined): string | null {
  if (!id || !map) return null;
  return map.get(id) || null;
}

function fmtDate(d: string | Date | null | undefined): string {
  if (!d) return '';
  if (typeof d === 'string') return d.slice(0, 10);
  return d.toISOString().slice(0, 10);
}

/** Compose a stable signature + a one-line label for a hotel item. */
// Itinerary items are loosely typed envelopes (booking_hotel_items,
// booking_transport_items, etc.). The diff helpers only read field-by-field
// for audit display — full entity typing would couple this file to several
// other modules and provide no additional safety here.
/* eslint-disable @typescript-eslint/no-explicit-any */

function hotelLabel(h: any, ctx: ItineraryAuditContext): { key: string; label: string } {
  const name = get(ctx.hotelNames, h.hotel_id) || h.hotel_id || 'Hotel';
  const ci = fmtDate(h.check_in);
  const co = fmtDate(h.check_out);
  // Room / meal moved into child `periods`. Pull the first period's
  // labels — matches how the diff read from the flat columns before.
  const firstPeriod = Array.isArray(h.periods) && h.periods.length > 0
    ? [...h.periods].sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0))[0]
    : null;
  const room = (firstPeriod?.room_type as string) || '—';
  const meal = (firstPeriod?.meal_plan as string) || '—';
  const nights = h.nights ?? '?';
  const key = `${h.hotel_id || ''}|${ci}|${co}|${room}|${meal}|${nights}`;
  const label = `${name} ${ci}→${co} (${nights}n, ${room}, ${meal})`;
  return { key, label };
}

function transportLabel(t: any, ctx: ItineraryAuditContext): { key: string; label: string } {
  const route =
    get(ctx.transportLabels, t.transport_service_id) || t.transport_service_id || 'Transport';
  const date = fmtDate(t.date);
  const pickup = t.pickup_time || '—:—';
  const mode = t.is_shared ? 'SIC' : 'PVT';
  const vt =
    get(ctx.vehicleTypeNames, t.vehicle_type_id) || (mode === 'SIC' ? 'shared coach' : '—');
  const qty = t.quantity ?? 1;
  const key = `${t.transport_service_id || ''}|${date}|${pickup}|${mode}|${t.vehicle_type_id || ''}|${qty}`;
  const label = `${route} ${date} ${pickup} (${mode}, ${vt}) ×${qty}`;
  return { key, label };
}

function activityLabel(a: any, ctx: ItineraryAuditContext): { key: string; label: string } {
  const name = get(ctx.activityNames, a.activity_id) || a.activity_id || 'Activity';
  const ticket = get(ctx.ticketNames, a.ticket_id) || (a.ticket_id ? a.ticket_id : 'no-ticket');
  const date = fmtDate(a.date);
  const slot = a.slot || '';
  const qty = a.quantity ?? 1;
  const ageGroup = a.age_group || '';
  const key = `${a.activity_id || ''}|${date}|${slot}|${a.ticket_id || ''}|${ageGroup}|${qty}`;
  const label = `${name} ${date}${slot ? ' ' + slot : ''} (${ticket}${ageGroup ? ', ' + ageGroup : ''}) ×${qty}`;
  return { key, label };
}

function specialLabel(s: any): { key: string; label: string } {
  const date = fmtDate(s.date);
  const price = s.total_price ?? 0;
  const key = `${s.service_name || ''}|${date}|${price}`;
  const label = `${s.service_name || 'Service'}${date ? ' ' + date : ''}`;
  return { key, label };
}

const LABELLERS: Record<
  string,
  (item: any, ctx: ItineraryAuditContext) => { key: string; label: string }
> = {
  hotel: hotelLabel,
  transport: transportLabel,
  activity: activityLabel,
  special: specialLabel,
};

/**
 * Diff two itinerary item lists by content-based key. Returns an
 * audit change row whose old/new values are pipe-joined human-readable
 * labels. If nothing changed, returns null so the caller can skip.
 *
 * Truncates each side to a reasonable length so a 50-row replacement
 * doesn't make the audit row unreadable — adds " (+N more)" if so.
 */
export function diffItineraryItems(
  field: string,
  type: 'hotel' | 'transport' | 'activity' | 'special',
  before: any[] | null | undefined,
  after: any[] | null | undefined,
  ctx: ItineraryAuditContext,
  options: { maxItems?: number } = {},
): AuditChange | null {
  const labeller = LABELLERS[type];
  const beforeRows = (before || []).map((it) => labeller(it, ctx));
  const afterRows = (after || []).map((it) => labeller(it, ctx));

  const beforeKeys = new Set(beforeRows.map((r) => r.key));
  const afterKeys = new Set(afterRows.map((r) => r.key));

  const removed = beforeRows.filter((r) => !afterKeys.has(r.key));
  const added = afterRows.filter((r) => !beforeKeys.has(r.key));

  if (removed.length === 0 && added.length === 0) return null;

  const max = options.maxItems ?? 6;
  const trim = (rows: { label: string }[]) => {
    if (rows.length === 0) return null;
    if (rows.length <= max) return rows.map((r) => r.label).join(' | ');
    return (
      rows
        .slice(0, max)
        .map((r) => r.label)
        .join(' | ') + ` (+${rows.length - max} more)`
    );
  };

  return {
    field,
    old_value:
      removed.length > 0
        ? `${beforeRows.length} rows. Removed: ${trim(removed)}`
        : `${beforeRows.length} rows`,
    new_value:
      added.length > 0
        ? `${afterRows.length} rows. Added: ${trim(added)}`
        : `${afterRows.length} rows`,
  };
}
