import type {
  FlightLegWindow,
  FlightPassengerGroup,
  FlightReportRequest,
  FlightReportRequestDraft,
  FlightSearchPreferences,
  NormalizedFlightReportRequest
} from "./types.js";

const DEFAULT_PREFERENCES: FlightSearchPreferences = {
  preferOneStop: true,
  maxLayoverHours: 6,
  normalizeCurrencyTo: "USD"
};

function cleanString(value: string | null | undefined): string | undefined {
  if (typeof value !== "string") {
    return undefined;
  }

  const trimmed = value.trim();
  return trimmed ? trimmed : undefined;
}

function uniqueStrings(values: Array<string | null | undefined> | undefined): string[] | undefined {
  if (!values?.length) {
    return undefined;
  }

  const cleaned = values
    .map((value) => cleanString(value))
    .filter((value): value is string => Boolean(value));

  return cleaned.length ? Array.from(new Set(cleaned)) : undefined;
}

function normalizeMarketCountry(value: string | null | undefined): string | null | undefined {
  const cleaned = cleanString(value);
  if (!cleaned) {
    return undefined;
  }

  const normalized = cleaned.toUpperCase();
  if (!/^[A-Z]{2}$/.test(normalized)) {
    throw new Error(
      `Invalid marketCountry "${value}". Use an ISO 3166-1 alpha-2 uppercase country code such as "TH" or "DE".`
    );
  }

  return normalized;
}

function isPlausibleEmail(value: string | null | undefined): boolean {
  if (!value) {
    return false;
  }

  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
}

function normalizePassengerGroups(
  groups: FlightReportRequestDraft["passengerGroups"]
): FlightPassengerGroup[] {
  return (groups || [])
    .filter((group): group is NonNullable<typeof group> => Boolean(group))
    .map((group, index) => ({
      id: cleanString(group.id) || `group-${index + 1}`,
      adults: Number(group.adults || 0),
      children: Number(group.children || 0) || undefined,
      infants: Number(group.infants || 0) || undefined,
      label: cleanString(group.label)
    }));
}

function normalizeLegs(legs: FlightReportRequestDraft["legs"]): FlightLegWindow[] {
  return (legs || [])
    .filter((leg): leg is NonNullable<typeof leg> => Boolean(leg))
    .map((leg, index) => ({
      id: cleanString(leg.id) || `leg-${index + 1}`,
      origin: cleanString(leg.origin) || "",
      destination: cleanString(leg.destination) || "",
      earliest: cleanString(leg.earliest),
      latest: cleanString(leg.latest),
      relativeToLegId: cleanString(leg.relativeToLegId),
      minDaysAfter:
        typeof leg.minDaysAfter === "number" && Number.isFinite(leg.minDaysAfter)
          ? leg.minDaysAfter
          : undefined,
      maxDaysAfter:
        typeof leg.maxDaysAfter === "number" && Number.isFinite(leg.maxDaysAfter)
          ? leg.maxDaysAfter
          : undefined,
      label: cleanString(leg.label)
    }));
}

function normalizeAssignments(
  draft: FlightReportRequestDraft["legAssignments"]
): FlightReportRequest["legAssignments"] {
  return (draft || [])
    .filter((assignment): assignment is NonNullable<typeof assignment> => Boolean(assignment))
    .map((assignment) => ({
      legId: cleanString(assignment.legId) || "",
      passengerGroupIds:
        uniqueStrings(
          (assignment.passengerGroupIds || []).map((value) =>
            typeof value === "string" ? value : undefined
          )
        ) || []
    }));
}

function normalizePreferences(
  preferences: FlightReportRequestDraft["preferences"]
): FlightSearchPreferences {
  const draft = preferences || {};
  return {
    ...DEFAULT_PREFERENCES,
    cabin: draft.cabin,
    maxStops: typeof draft.maxStops === "number" ? draft.maxStops : undefined,
    preferOneStop:
      typeof draft.preferOneStop === "boolean"
        ? draft.preferOneStop
        : DEFAULT_PREFERENCES.preferOneStop,
    maxLayoverHours:
      typeof draft.maxLayoverHours === "number"
        ? draft.maxLayoverHours
        : DEFAULT_PREFERENCES.maxLayoverHours,
    excludeAirlines: uniqueStrings(draft.excludeAirlines),
    excludeCountries: uniqueStrings(draft.excludeCountries),
    excludeAirports: uniqueStrings(draft.excludeAirports),
    flexibleDates:
      typeof draft.flexibleDates === "boolean" ? draft.flexibleDates : undefined,
    requireAirlineDirectCrossCheck:
      typeof draft.requireAirlineDirectCrossCheck === "boolean"
        ? draft.requireAirlineDirectCrossCheck
        : undefined,
    specialConstraints: uniqueStrings(draft.specialConstraints),
    geoPricingMarket: cleanString(draft.geoPricingMarket),
    marketCountry: normalizeMarketCountry(draft.marketCountry),
    normalizeCurrencyTo: "USD"
  };
}

function collectMissingSearchInputs(request: FlightReportRequest): string[] {
  const missing = new Set<string>();

  if (!request.legs.length) {
    missing.add("trip legs");
  }

  if (!request.passengerGroups.length) {
    missing.add("passenger groups");
  }

  for (const group of request.passengerGroups) {
    const total = group.adults + (group.children || 0) + (group.infants || 0);
    if (total <= 0) {
      missing.add(`traveler count for passenger group ${group.label || group.id}`);
    }
  }

  const legIds = new Set(request.legs.map((leg) => leg.id));
  const groupIds = new Set(request.passengerGroups.map((group) => group.id));

  for (const leg of request.legs) {
    const label = leg.label || leg.id;
    if (!leg.origin || !leg.destination) {
      missing.add(`origin and destination for ${label}`);
    }

    const hasAbsoluteWindow = Boolean(leg.earliest || leg.latest);
    const hasRelativeWindow = Boolean(
      leg.relativeToLegId &&
        (typeof leg.minDaysAfter === "number" || typeof leg.maxDaysAfter === "number")
    );

    if (!hasAbsoluteWindow && !hasRelativeWindow) {
      missing.add(`date window for ${label}`);
    }

    if (leg.relativeToLegId && !legIds.has(leg.relativeToLegId)) {
      missing.add(`valid reference leg for ${label}`);
    }
  }

  if (!request.legAssignments.length) {
    missing.add("passenger assignments for every leg");
  }

  for (const leg of request.legs) {
    const label = leg.label || leg.id;
    const assignment = request.legAssignments.find((entry) => entry.legId === leg.id);
    if (!assignment) {
      missing.add(`passenger assignments for ${label}`);
      continue;
    }

    if (!assignment.passengerGroupIds.length) {
      missing.add(`assigned travelers for ${label}`);
      continue;
    }

    if (assignment.passengerGroupIds.some((groupId) => !groupIds.has(groupId))) {
      missing.add(`valid passenger-group references for ${label}`);
    }
  }

  return Array.from(missing);
}

export function normalizeFlightReportRequest(
  draft: FlightReportRequestDraft
): NormalizedFlightReportRequest {
  const normalizedRecipientEmail = cleanString(draft.recipientEmail) || null;
  const request: FlightReportRequest = {
    tripName: cleanString(draft.tripName),
    legs: normalizeLegs(draft.legs),
    passengerGroups: normalizePassengerGroups(draft.passengerGroups),
    legAssignments: normalizeAssignments(draft.legAssignments),
    recipientEmail: normalizedRecipientEmail,
    preferences: normalizePreferences(draft.preferences)
  };

  const missingSearchInputs = collectMissingSearchInputs(request);
  const missingDeliveryInputs = !request.recipientEmail
    ? ["recipient email"]
    : isPlausibleEmail(request.recipientEmail)
      ? []
      : ["valid recipient email"];
  const warnings: string[] = [];

  if (!request.recipientEmail) {
    warnings.push(
      "Recipient email is still missing. Ask for it before rendering or sending the PDF report."
    );
  } else if (!isPlausibleEmail(request.recipientEmail)) {
    warnings.push(
      "Recipient email looks malformed. Ask for a corrected email address before rendering or sending the PDF report."
    );
  }

  if (request.preferences.marketCountry && !request.preferences.geoPricingMarket) {
    warnings.push(
      `Market-localized search is explicit for this run. Connect VPN to ${request.preferences.marketCountry} only for the bounded search phase, then disconnect before ranking/render/delivery.`
    );
  }

  return {
    request,
    readyToSearch: missingSearchInputs.length === 0,
    missingInputs: [...missingSearchInputs, ...missingDeliveryInputs],
    missingSearchInputs,
    missingDeliveryInputs,
    warnings
  };
}

export const DFW_BLQ_2026_PROMPT_DRAFT: FlightReportRequestDraft = {
  tripName: "DFW ↔ BLQ flight report",
  legs: [
    {
      id: "outbound",
      origin: "DFW",
      destination: "BLQ",
      earliest: "2026-05-30",
      latest: "2026-06-07",
      label: "Outbound"
    },
    {
      id: "return-pair",
      origin: "BLQ",
      destination: "DFW",
      relativeToLegId: "outbound",
      minDaysAfter: 6,
      maxDaysAfter: 10,
      label: "Return for 2 adults"
    },
    {
      id: "return-solo",
      origin: "BLQ",
      destination: "DFW",
      earliest: "2026-06-28",
      latest: "2026-07-05",
      label: "Return for 1 adult"
    }
  ],
  passengerGroups: [
    {
      id: "pair",
      adults: 2,
      label: "2 adults traveling together"
    },
    {
      id: "solo",
      adults: 1,
      label: "1 adult returning separately"
    }
  ],
  legAssignments: [
    {
      legId: "outbound",
      passengerGroupIds: ["pair", "solo"]
    },
    {
      legId: "return-pair",
      passengerGroupIds: ["pair"]
    },
    {
      legId: "return-solo",
      passengerGroupIds: ["solo"]
    }
  ],
  recipientEmail: "stefano@fiorinis.com",
  preferences: {
    preferOneStop: true,
    maxStops: 1,
    maxLayoverHours: 6,
    flexibleDates: true,
    excludeAirlines: ["Turkish Airlines"],
    excludeCountries: ["TR"],
    requireAirlineDirectCrossCheck: true,
    specialConstraints: [
      "Exclude itineraries operated by airlines based in Arab or Middle Eastern countries.",
      "Exclude itineraries routing through airports in Arab or Middle Eastern countries.",
      "Start from a fresh browser/session profile for this search."
    ],
    geoPricingMarket: "Thailand",
    marketCountry: "TH",
    normalizeCurrencyTo: "USD"
  }
};
