


// "use strict";

// const xml2js = require("xml2js");
// const travelportClient = require("../utils/travelportClient");
// const fs = require("fs");
// const path = require("path");

// const parser = new xml2js.Parser({ explicitArray: false });

// const AIR_NS = "http://www.travelport.com/schema/air_v54_0";
// const COM_NS = "http://www.travelport.com/schema/common_v54_0";

// const { TARGET_BRANCH } = process.env;

// // Carriers whose content comes from ACH (hosted/NDC). Same discriminator the
// // booking service uses, so both stay in sync. Add more ACH carriers here later.
// const ACH_CARRIERS = ["6E"];

// /* ── HELPERS ── */

// function saveDebugXml(fileName, xmlData) {
//   try {
//     const dir = path.resolve(__dirname, "..", "debug-xml");
//     if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
//     fs.writeFileSync(path.join(dir, fileName), xmlData);
//   } catch (err) {
//     console.error(err.message);
//   }
// }

// function checkIsACH(segments) {
//   return segments.some((s) => ACH_CARRIERS.includes(s.carrier));
// }

// function mapPassengerCode(code) {
//   if (code === "CHD") return "CNN";
//   return code;
// }

// /**
//  * Connection only for NON-LAST segment in each group (shared 1G + ACH).
//  */
// function addConnectionFlags(segments) {
//   const map = new Map();

//   segments.forEach((s, i) => {
//     if (!map.has(s.group)) map.set(s.group, []);
//     map.get(s.group).push({ ...s, index: i });
//   });

//   const updated = [...segments];

//   for (const group of map.values()) {
//     group.forEach((g, idx) => {
//       updated[g.index].connection = idx !== group.length - 1; // ONLY non-last
//     });
//   }

//   return updated;
// }

// /** Build a { segmentKey -> bookingRequirement } lookup for the ACH path. */
// function buildBookingReqMap(bookingRequirements = []) {
//   const map = {};
//   for (const br of bookingRequirements) {
//     if (br && br.segmentKey) map[br.segmentKey] = br;
//   }
//   return map;
// }

// /* ── XML BUILDERS ── */

// function buildPassengersXML(passengers, isACH) {
//   let xml = "";
//   const passengerKeys = [];

//   const priority = { ADT: 1, INF: 2, CNN: 3, CHD: 3 };
//   const typeIndexMap = {}; // track index per passenger type

//   const sorted = [...passengers].sort(
//     (a, b) => (priority[a.code] || 99) - (priority[b.code] || 99)
//   );

//   let seq = 0; // running counter for ACH SearchPassenger Key (P_1, P_2, ...)

//   sorted.forEach((p) => {
//     const code = mapPassengerCode(p.code, isACH);
//     const key = p.bookingTravelerRef;

//     if (typeIndexMap[code] === undefined) typeIndexMap[code] = 0;
//     const passengerIndex = typeIndexMap[code]++;

//     passengerKeys.push({
//       code: p.code,
//       mappedCode: code,
//       age: p.age,
//       passengerIndex, // 0, 1, 2... per type
//       key,            // unique key for this individual passenger
//     });

//     seq++;
//     // ACH adds a SearchPassenger Key (P_1, P_2...). 1G output is unchanged.
//     const paxKeyAttr = isACH ? `Key="P_${seq}" ` : "";

//     xml += `
// <com:SearchPassenger ${paxKeyAttr}Code="${code}" BookingTravelerRef="${key}" ${p.age ? `Age="${p.age}"` : ""} ${p.code === "INF" && !isACH ? `PricePTCOnly="true"` : ""}/>`;
//   });

//   return { xml, passengerKeys };
// }

// /** 1G AirSegment (unchanged GDS shape). */
// // function buildGdsAirSegmentXML(s, isACH) {
// //   return `<air:AirSegment Key="${s.segmentKey}" Group="${s.group}" Carrier="${s.carrier}" FlightNumber="${s.flightNumber}" Origin="${s.origin}" Destination="${s.destination}" DepartureTime="${s.departureTime}" ArrivalTime="${s.arrivalTime}" FlightTime="${s.flightTime}" Distance="${s.distance}" ETicketability="${s.eTicketability}" Equipment="${s.equipment}" ChangeOfPlane="${s.changeOfPlane}" ParticipantLevel="${s.participantLevel}" LinkAvailability="${s.linkAvailability}" PolledAvailabilityOption="${s.polledAvailabilityOption}" OptionalServicesIndicator="${s.optionalServicesIndicator}" ProviderCode="${isACH ? "ACH" : "1G"}" ClassOfService="${s.classOfService}" AvailabilitySource="${s.availabilitySource}" AvailabilityDisplayType="${s.availabilityDisplayType}">${s.connection ? "<air:Connection/>" : ""}</air:AirSegment>`;
// // }


// function buildGdsAirSegmentXML(s, isACH) {
//   return `<air:AirSegment Key="${s.segmentKey}" Group="${s.group}" Carrier="${s.carrier}" FlightNumber="${s.flightNumber}" Origin="${s.origin}" Destination="${s.destination}" DepartureTime="${s.departureTime}" ArrivalTime="${s.arrivalTime}" FlightTime="${s.flightTime}" Distance="${s.distance}" ETicketability="${s.eTicketability}" Equipment="${s.equipment}" ChangeOfPlane="${s.changeOfPlane}" ParticipantLevel="${s.participantLevel}" LinkAvailability="${s.linkAvailability}" PolledAvailabilityOption="${s.polledAvailabilityOption}" OptionalServicesIndicator="${s.optionalServicesIndicator}" ProviderCode="${isACH ? "ACH" : "1G"}" ClassOfService="${s.classOfService}" AvailabilitySource="${s.availabilitySource}" AvailabilityDisplayType="${s.availabilityDisplayType}">${s.connection ? "<air:Connection/>" : ""}</air:AirSegment>`;
// }

// /** ACH / 6E AirSegment: host bindings kept, GDS availability attrs dropped.
//  *  ClassOfService + HostTokenRef come from the matching bookingRequirement. */
// function buildAchAirSegmentXML(s, br) {
//   const classOfService = (br && br.bookingCode) || s.classOfService || "";
//   const hostTokenRef   = (br && br.hostTokenRef) || s.hostTokenRef || "";

//   let attrs =
//     `Key="${s.segmentKey}" Group="${s.group}" ` +
//     `Carrier="${s.carrier}" FlightNumber="${s.flightNumber}" ` +
//     `Origin="${s.origin}" Destination="${s.destination}" ` +
//     `DepartureTime="${s.departureTime}" ArrivalTime="${s.arrivalTime}" ` +
//     `ProviderCode="ACH" SupplierCode="${s.supplierCode || s.carrier}" ` +
//     `ClassOfService="${classOfService}" FlightTime="${s.flightTime}" ` +
//     `Equipment="${s.equipment}" ChangeOfPlane="${s.changeOfPlane}" ` +
//     `Status="${s.status || "KK"}" OptionalServicesIndicator="${s.optionalServicesIndicator || "false"}"`;
//   if (s.APISRequirementsRef) attrs += ` APISRequirementsRef="${s.APISRequirementsRef}"`;
//   if (hostTokenRef)          attrs += ` HostTokenRef="${hostTokenRef}"`;

// //   return `<air:AirSegment ${attrs}>${s.connection ? "<air:Connection/>" : ""}</air:AirSegment>`;

//   return `<air:AirSegment ${attrs}>${s.connection ? "<air:Connection/>" : ""}</air:AirSegment>`;
// }

// /** AirItinerary inner XML. For ACH, segments are followed by their HostToken(s). */
// function buildAirItineraryXML(segments, isACH, bookingReqMap = {}) {
//   if (!isACH) {
//     return segments.map((s) => buildGdsAirSegmentXML(s, isACH)).join("");
//   }

//   // ACH: clean segments, then one HostToken per unique hostTokenRef
//   const segXML = segments
//     .map((s) => buildAchAirSegmentXML(s, bookingReqMap[s.segmentKey]))
//     .join("");

//   const seen = new Set();
//   let hostTokensXML = "";
//   for (const s of segments) {
//     const br = bookingReqMap[s.segmentKey];
//     if (br && br.hostTokenRef && br.hostToken && !seen.has(br.hostTokenRef)) {
//       seen.add(br.hostTokenRef);
//       hostTokensXML += `<com:HostToken Key="${br.hostTokenRef}"><![CDATA[${br.hostToken}]]></com:HostToken>`;
//     }
//   }

//   return segXML + hostTokensXML;
// }

// /** 1G pricing modifier (unchanged). */
// function buildAirPricingModifiersXML() {
//   return `<air:AirPricingModifiers ETicketability="Yes" FaresIndicator="AllFares"></air:AirPricingModifiers>`;
// }

// /** ACH pricing modifier — request fare-family branding back in the response. */
// function buildAchPricingModifiersXML() {
//   return `<air:AirPricingModifiers><air:BrandModifiers><air:FareFamilyDisplay ModifierType="FareFamily"/></air:BrandModifiers></air:AirPricingModifiers>`;
// }

// /** ACH AirPricingCommand — one AirSegmentPricingModifiers per segment,
//  *  carrying the selected FareBasisCode from bookingRequirements. */
// function buildAirPricingCommandXML(segments, bookingReqMap = {}) {
//   const mods = segments
//     .map((s) => {
//       const br = bookingReqMap[s.segmentKey];
//       const fareBasis = br && br.fareBasis ? ` FareBasisCode="${br.fareBasis}"` : "";
//       return `<air:AirSegmentPricingModifiers AirSegmentRef="${s.segmentKey}"${fareBasis}/>`;
//     })
//     .join("");
//   return `<air:AirPricingCommand>${mods}</air:AirPricingCommand>`;
// }

// function buildXML(payload, traceId) {
//   const isACH = checkIsACH(payload.segments);

//   const { xml: paxXML, passengerKeys } = buildPassengersXML(
//     payload.passengers,
//     isACH
//   );

//   const bookingReqMap = buildBookingReqMap(payload.bookingRequirements);
//   const itineraryXML  = buildAirItineraryXML(payload.segments, isACH, bookingReqMap);

//   let xml;

//   if (isACH) {
//     // ── ACH / 6E request ──────────────────────────────────────────────────
//     const pricingModifiersXML = buildAchPricingModifiersXML();
//     const pricingCommandXML   = buildAirPricingCommandXML(payload.segments, bookingReqMap);

//     xml = `<?xml version="1.0"?>
// <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:air="${AIR_NS}" xmlns:com="${COM_NS}">
// <soapenv:Body>
// <air:AirPriceReq AuthorizedBy="BOBROS" TargetBranch="${TARGET_BRANCH}" TraceId="${traceId}">
// <com:BillingPointOfSaleInfo OriginApplication="UAPI"/>
// <air:AirItinerary>${itineraryXML}</air:AirItinerary>
// ${pricingModifiersXML}
// ${paxXML}
// ${pricingCommandXML}
// <com:FormOfPayment Type="AgencyPayment">
// <com:AgencyPayment AgencyBillingIdentifier="TSTTP01"/>
// </com:FormOfPayment>
// </air:AirPriceReq>
// </soapenv:Body>
// </soapenv:Envelope>`;
//   } else {
//     // ── 1G request (unchanged) ────────────────────────────────────────────
//     const pricingModifiersXML = buildAirPricingModifiersXML();

//     xml = `<?xml version="1.0"?>
// <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:air="${AIR_NS}" xmlns:com="${COM_NS}">
// <soapenv:Body>
// <air:AirPriceReq TraceId="${traceId}" AuthorizedBy="BOBROS" CheckOBFees="All" TargetBranch="${TARGET_BRANCH}"  FareRuleType="long">
// <com:BillingPointOfSaleInfo OriginApplication="UAPI"/>
// <air:AirItinerary>${itineraryXML}</air:AirItinerary>
// ${pricingModifiersXML}
// ${paxXML}
// </air:AirPriceReq>
// </soapenv:Body>
// </soapenv:Envelope>`;
//   }

//   return { xml, passengerKeys };
// }

// /* ── MAIN ── */

// exports.priceFlight = async (payload, traceId) => {
//   const timestamp = Date.now();

//   const updatedPayload = {
//     ...payload,
//     segments: addConnectionFlags(payload.segments),
//   };

//   try {
//     const { xml, passengerKeys } = buildXML(updatedPayload, traceId);
//     console.log(JSON.stringify(xml));
//     saveDebugXml(`AirPricingReq-${traceId}-${timestamp}.xml`, xml);

//     const response = await travelportClient.post(
//       process.env.TRAVELPORT_AIR_SERVICE,
//       xml
//     );

//     saveDebugXml(`AirpricingResponse-${traceId}-${timestamp}.xml`, response.data);

//     const json = await parser.parseStringPromise(response.data);

//     return {
//       success: true,
//       attempt: 1,
//       passengerKeys, // preserved
//       data: json,
//     };

//   } catch (err) {
//     return {
//       success: false,
//       error: err.response?.data || err.message,
//     };
//   }
// };




"use strict";

const xml2js = require("xml2js");
const travelportClient = require("../utils/travelportClient");
const fs = require("fs");
const path = require("path");

const parser = new xml2js.Parser({ explicitArray: false });

const AIR_NS = "http://www.travelport.com/schema/air_v54_0";
const COM_NS = "http://www.travelport.com/schema/common_v54_0";

const { TARGET_BRANCH } = process.env;

// Carriers whose content comes from ACH (hosted/NDC). Same discriminator the
// booking service uses, so both stay in sync. Add more ACH carriers here later.
const ACH_CARRIERS = ["6E"];

/* ── HELPERS ── */

function saveDebugXml(fileName, xmlData) {
  try {
    const dir = path.resolve(__dirname, "..", "debug-xml");
    if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
    fs.writeFileSync(path.join(dir, fileName), xmlData);
  } catch (err) {
    console.error(err.message);
  }
}

function checkIsACH(segments) {
  return segments.some((s) => ACH_CARRIERS.includes(s.carrier));
}

function mapPassengerCode(code) {
  if (code === "CHD") return "CNN";
  return code;
}

/**
 * Connection only for NON-LAST segment in each group (shared 1G + ACH).
 */
function addConnectionFlags(segments) {
  const map = new Map();

  segments.forEach((s, i) => {
    if (!map.has(s.group)) map.set(s.group, []);
    map.get(s.group).push({ ...s, index: i });
  });

  const updated = [...segments];

  for (const group of map.values()) {
    group.forEach((g, idx) => {
      updated[g.index].connection = idx !== group.length - 1; // ONLY non-last
    });
  }

  return updated;
}

/** Build a { segmentKey -> bookingRequirement } lookup for the ACH path. */
function buildBookingReqMap(bookingRequirements = []) {
  const map = {};
  for (const br of bookingRequirements) {
    if (br && br.segmentKey) map[br.segmentKey] = br;
  }
  return map;
}

/* ── XML BUILDERS ── */

function buildPassengersXML(passengers, isACH) {
  let xml = "";
  const passengerKeys = [];

  const priority = { ADT: 1, INF: 2, CNN: 3, CHD: 3 };
  const typeIndexMap = {}; // track index per passenger type

  const sorted = [...passengers].sort(
    (a, b) => (priority[a.code] || 99) - (priority[b.code] || 99)
  );

  let seq = 0; // running counter for ACH SearchPassenger Key (P_1, P_2, ...)

  sorted.forEach((p) => {
    const code = mapPassengerCode(p.code, isACH);
    const key = p.bookingTravelerRef;

    if (typeIndexMap[code] === undefined) typeIndexMap[code] = 0;
    const passengerIndex = typeIndexMap[code]++;

    passengerKeys.push({
      code: p.code,
      mappedCode: code,
      age: p.age,
      passengerIndex, // 0, 1, 2... per type
      key,            // unique key for this individual passenger
    });

    seq++;
    // ACH adds a SearchPassenger Key (P_1, P_2...). 1G output is unchanged.
    const paxKeyAttr = isACH ? `Key="P_${seq}" ` : "";

    xml += `
<com:SearchPassenger ${paxKeyAttr}Code="${code}" BookingTravelerRef="${key}" ${p.age ? `Age="${p.age}"` : ""} ${p.code === "INF" && !isACH ? `PricePTCOnly="true"` : ""}/>`;
  });

  return { xml, passengerKeys };
}

/** 1G AirSegment (GDS shape).
 *  Only attributes that are actually available from the JSON search response
 *  are emitted. The availability/optimization attributes that uAPI's classic
 *  AirSegment can carry (ETicketability, ChangeOfPlane, ParticipantLevel,
 *  LinkAvailability, PolledAvailabilityOption, OptionalServicesIndicator,
 *  AvailabilityDisplayType) are NOT returned by CatalogProductOfferings, so
 *  they are intentionally omitted instead of being sent as "undefined". */
function buildGdsAirSegmentXML(s, isACH) {
  return `<air:AirSegment Key="${s.segmentKey}" Group="${s.group}" Carrier="${s.carrier}" FlightNumber="${s.flightNumber}" Origin="${s.origin}" Destination="${s.destination}" DepartureTime="${s.departureTime}" ArrivalTime="${s.arrivalTime}" FlightTime="${s.flightTime}" Distance="${s.distance}" Equipment="${s.equipment}" ProviderCode="${isACH ? "ACH" : "1G"}" ClassOfService="${s.classOfService}" AvailabilitySource="${s.availabilitySource}">${s.connection ? "<air:Connection/>" : ""}</air:AirSegment>`;
}

/** ACH / 6E AirSegment: host bindings kept, GDS availability attrs dropped.
 *  ClassOfService + HostTokenRef come from the matching bookingRequirement. */
function buildAchAirSegmentXML(s, br) {
  const classOfService = (br && br.bookingCode) || s.classOfService || "";
  const hostTokenRef   = (br && br.hostTokenRef) || s.hostTokenRef || "";

  let attrs =
    `Key="${s.segmentKey}" Group="${s.group}" ` +
    `Carrier="${s.carrier}" FlightNumber="${s.flightNumber}" ` +
    `Origin="${s.origin}" Destination="${s.destination}" ` +
    `DepartureTime="${s.departureTime}" ArrivalTime="${s.arrivalTime}" ` +
    `ProviderCode="ACH" SupplierCode="${s.supplierCode || s.carrier}" ` +
    `ClassOfService="${classOfService}" FlightTime="${s.flightTime}" ` +
    `Equipment="${s.equipment}" ChangeOfPlane="${s.changeOfPlane}" ` +
    `Status="${s.status || "KK"}" OptionalServicesIndicator="${s.optionalServicesIndicator || "false"}"`;
  if (s.APISRequirementsRef) attrs += ` APISRequirementsRef="${s.APISRequirementsRef}"`;
  if (hostTokenRef)          attrs += ` HostTokenRef="${hostTokenRef}"`;

  return `<air:AirSegment ${attrs}>${s.connection ? "<air:Connection/>" : ""}</air:AirSegment>`;
}

/** AirItinerary inner XML. For ACH, segments are followed by their HostToken(s). */
function buildAirItineraryXML(segments, isACH, bookingReqMap = {}) {
  if (!isACH) {
    return segments.map((s) => buildGdsAirSegmentXML(s, isACH)).join("");
  }

  // ACH: clean segments, then one HostToken per unique hostTokenRef
  const segXML = segments
    .map((s) => buildAchAirSegmentXML(s, bookingReqMap[s.segmentKey]))
    .join("");

  const seen = new Set();
  let hostTokensXML = "";
  for (const s of segments) {
    const br = bookingReqMap[s.segmentKey];
    if (br && br.hostTokenRef && br.hostToken && !seen.has(br.hostTokenRef)) {
      seen.add(br.hostTokenRef);
      hostTokensXML += `<com:HostToken Key="${br.hostTokenRef}"><![CDATA[${br.hostToken}]]></com:HostToken>`;
    }
  }

  return segXML + hostTokensXML;
}

/** 1G pricing modifier (unchanged). */
function buildAirPricingModifiersXML() {
  return `<air:AirPricingModifiers ETicketability="Yes" FaresIndicator="AllFares"></air:AirPricingModifiers>`;
}

/** ACH pricing modifier — request fare-family branding back in the response. */
function buildAchPricingModifiersXML() {
  return `<air:AirPricingModifiers><air:BrandModifiers><air:FareFamilyDisplay ModifierType="FareFamily"/></air:BrandModifiers></air:AirPricingModifiers>`;
}

/** ACH AirPricingCommand — one AirSegmentPricingModifiers per segment,
 *  carrying the selected FareBasisCode from bookingRequirements. */
function buildAirPricingCommandXML(segments, bookingReqMap = {}) {
  const mods = segments
    .map((s) => {
      const br = bookingReqMap[s.segmentKey];
      const fareBasis = br && br.fareBasis ? ` FareBasisCode="${br.fareBasis}"` : "";
      return `<air:AirSegmentPricingModifiers AirSegmentRef="${s.segmentKey}"${fareBasis}/>`;
    })
    .join("");
  return `<air:AirPricingCommand>${mods}</air:AirPricingCommand>`;
}

function buildXML(payload, traceId) {
  const isACH = checkIsACH(payload.segments);

  const { xml: paxXML, passengerKeys } = buildPassengersXML(
    payload.passengers,
    isACH
  );

  const bookingReqMap = buildBookingReqMap(payload.bookingRequirements);
  const itineraryXML  = buildAirItineraryXML(payload.segments, isACH, bookingReqMap);

  let xml;

  if (isACH) {
    // ── ACH / 6E request ──────────────────────────────────────────────────
    const pricingModifiersXML = buildAchPricingModifiersXML();
    const pricingCommandXML   = buildAirPricingCommandXML(payload.segments, bookingReqMap);

    xml = `<?xml version="1.0"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:air="${AIR_NS}" xmlns:com="${COM_NS}">
<soapenv:Body>
<air:AirPriceReq AuthorizedBy="BOBROS" TargetBranch="${TARGET_BRANCH}" TraceId="${traceId}">
<com:BillingPointOfSaleInfo OriginApplication="UAPI"/>
<air:AirItinerary>${itineraryXML}</air:AirItinerary>
${pricingModifiersXML}
${paxXML}
${pricingCommandXML}
<com:FormOfPayment Type="AgencyPayment">
<com:AgencyPayment AgencyBillingIdentifier="TSTTP01"/>
</com:FormOfPayment>
</air:AirPriceReq>
</soapenv:Body>
</soapenv:Envelope>`;
  } else {
    // ── 1G request (unchanged) ────────────────────────────────────────────
    const pricingModifiersXML = buildAirPricingModifiersXML();

    xml = `<?xml version="1.0"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:air="${AIR_NS}" xmlns:com="${COM_NS}">
<soapenv:Body>
<air:AirPriceReq TraceId="${traceId}" AuthorizedBy="BOBROS" CheckOBFees="All" TargetBranch="${TARGET_BRANCH}"  FareRuleType="long">
<com:BillingPointOfSaleInfo OriginApplication="UAPI"/>
<air:AirItinerary>${itineraryXML}</air:AirItinerary>
${pricingModifiersXML}
${paxXML}
</air:AirPriceReq>
</soapenv:Body>
</soapenv:Envelope>`;
  }

  return { xml, passengerKeys };
}

/* ── MAIN ── */

exports.priceFlight = async (payload, traceId) => {
  const timestamp = Date.now();

  const updatedPayload = {
    ...payload,
    segments: addConnectionFlags(payload.segments),
  };

  try {
    const { xml, passengerKeys } = buildXML(updatedPayload, traceId);
    console.log(JSON.stringify(xml));
    saveDebugXml(`AirPricingReq-${traceId}-${timestamp}.xml`, xml);

    const response = await travelportClient.post(
      process.env.TRAVELPORT_AIR_SERVICE,
      xml
    );

    saveDebugXml(`AirpricingResponse-${traceId}-${timestamp}.xml`, response.data);

    const json = await parser.parseStringPromise(response.data);

    return {
      success: true,
      attempt: 1,
      passengerKeys, // preserved
      data: json,
    };

  } catch (err) {
    return {
      success: false,
      error: err.response?.data || err.message,
    };
  }
};
