const https  = require('https');
const http   = require('http');
const zlib   = require('zlib');
const xml2js = require('xml2js');

// ─────────────────────────────────────────────────────────────────────────────
// [Common #1 & #2] Single schema version constant
// Change this ONE line to upgrade all APIs at once.
// ─────────────────────────────────────────────────────────────────────────────
const SCHEMA_VERSION = 'v54_0';

// ─────────────────────────────────────────────────────────────────────────────
// CONFIG — ENV KEY RESOLUTION
//
// Priority for SOAP URL:
//   1. TRAVELPORT_SOAP_URL     standard key (add this to .env)
//   2. TRAVELPORT_AIR_SERVICE  key already in your .env
//   3. TRAVELPORT_URL          fallback key already in your .env
//   4. hardcoded Americas URL  last resort
// ─────────────────────────────────────────────────────────────────────────────
const TRAVELPORT_CONFIG = {
  soapUrl: (
    process.env.TRAVELPORT_SOAP_URL    ||
    process.env.TRAVELPORT_AIR_SERVICE ||
    process.env.TRAVELPORT_URL         ||
    'https://apac.universal-api.pp.travelport.com/B2BGateway/connect/uAPI/AirService'
  ),
  username:  process.env.TRAVELPORT_USERNAME || '',
  password:  process.env.TRAVELPORT_PASSWORD || '',
  timeoutMs: parseInt(process.env.TRAVELPORT_TIMEOUT_MS || '30000', 10),

  // [Common #1 & #2] Both namespaces always derived from the same SCHEMA_VERSION
  namespaces: {
    air: `http://www.travelport.com/schema/air_${SCHEMA_VERSION}`,
    com: `http://www.travelport.com/schema/common_${SCHEMA_VERSION}`,
    s:   'http://schemas.xmlsoap.org/soap/envelope/',
    xsd: 'http://www.w3.org/2001/XMLSchema',
    xsi: 'http://www.w3.org/2001/XMLSchema-instance',
  },
};

// ─────────────────────────────────────────────────────────────────────────────
// STARTUP CONFIG VERIFICATION — printed once when module loads, not per-request
// Immediately reveals wrong URL or missing credentials at startup.
// ─────────────────────────────────────────────────────────────────────────────
console.log('\n╔══════════════════════════════════════════════════════════╗');
console.log('║     AirRetrieveDocumentService — CONFIG LOADED           ║');
console.log('╚══════════════════════════════════════════════════════════╝');
console.log(`  Schema Version  : ${SCHEMA_VERSION}`);
console.log(`  SOAP URL        : ${TRAVELPORT_CONFIG.soapUrl}`);
console.log(`  Username        : ${TRAVELPORT_CONFIG.username
  ? '✓ SET (' + TRAVELPORT_CONFIG.username.substring(0, 25) + '...)'
  : '✗ MISSING — set TRAVELPORT_USERNAME in .env'}`);
console.log(`  Password        : ${TRAVELPORT_CONFIG.password ? '✓ SET' : '✗ MISSING — set TRAVELPORT_PASSWORD in .env'}`);
console.log(`  Provider Code   : ${process.env.PROVIDER_CODE  || '✗ MISSING — set PROVIDER_CODE in .env (e.g. 1G)'}`);
console.log(`  Target Branch   : ${process.env.TARGET_BRANCH  || '✗ MISSING — set TARGET_BRANCH in .env'}`);
console.log(`  Authorized By   : ${process.env.AUTHORIZED_BY  || '✗ MISSING — set AUTHORIZED_BY in .env'}`);
console.log(`  Timeout         : ${TRAVELPORT_CONFIG.timeoutMs}ms`);
console.log('──────────────────────────────────────────────────────────\n');

// ─────────────────────────────────────────────────────────────────────────────
// [Common #3, #8, #10] Persistent HTTPS connection pool
// keepAlive    → reuse TCP+TLS connections (100-300ms saved per request)
// 512KB buffer → Travelport requires >= 500K TCP receive buffer
// ─────────────────────────────────────────────────────────────────────────────
const httpsAgent = new https.Agent({
  keepAlive:        true,
  keepAliveMsecs:   30000,
  maxSockets:       50,
  maxFreeSockets:   10,
  socketBufferSize: 512000,
  timeout:          TRAVELPORT_CONFIG.timeoutMs,
});

// XML injection prevention
const escapeXml = (str) => {
  if (str == null) return '';
  return String(str)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&apos;');
};

// ─────────────────────────────────────────────────────────────────────────────
// STEP 1 — BUILD SOAP XML
//
// Dynamic element: <air:AirReservationLocatorCode>
//   Present  → Option 1: retrieve all tickets for that PNR
//   Absent   → Option 2: retrieve specific ticket by TicketNumber only
// ─────────────────────────────────────────────────────────────────────────────
const buildSoapXml = (payload, requestId) => {
  const {
    traceId, targetBranch, providerCode, providerLocatorCode,
    ticketNumber, returnRestrictions, returnPricing, retrieveMCO,
    authorizedBy, originApplication, retrieveAgencyInfo,
    retrieveCannedRemark, airReservationLocatorCode,
  } = payload;

  const { air, com, s, xsd, xsi } = TRAVELPORT_CONFIG.namespaces;

  const locatorCodeXml = airReservationLocatorCode
    ? `\n      <air:AirReservationLocatorCode>${escapeXml(airReservationLocatorCode)}</air:AirReservationLocatorCode>`
    : '';

  console.log(`[${requestId}]   [XML] Schema                    : ${SCHEMA_VERSION}  [Common #1 & #2]`);
  console.log(`[${requestId}]   [XML] AirReservationLocatorCode : ${airReservationLocatorCode ? `INCLUDED → Option 1` : 'OMITTED → Option 2'}`);
  console.log(`[${requestId}]   [XML] RetrieveAgencyInfo        : ${retrieveAgencyInfo}`);
  console.log(`[${requestId}]   [XML] RetrieveCannedRemark      : ${retrieveCannedRemark}`);
  console.log(`[${requestId}]   [XML] AuthorizedBy              : ${authorizedBy}`);
  console.log(`[${requestId}]   [XML] OriginApplication         : ${originApplication}`);

  return `<s:Envelope xmlns:s="${s}">
  <s:Body xmlns:xsd="${xsd}" xmlns:xsi="${xsi}">
    <air:AirRetrieveDocumentReq
      TraceId="${escapeXml(traceId)}"
      TargetBranch="${escapeXml(targetBranch)}"
      ReturnRestrictions="${String(returnRestrictions)}"
      ReturnPricing="${String(returnPricing)}"
      RetrieveMCO="${String(retrieveMCO)}"
      RetrieveAgencyInfo="${String(retrieveAgencyInfo)}"
      RetrieveCannedRemark="${String(retrieveCannedRemark)}"
      ProviderCode="${escapeXml(providerCode)}"
      ProviderLocatorCode="${escapeXml(providerLocatorCode)}"
      AuthorizedBy="${escapeXml(authorizedBy)}"
      xmlns:air="${air}"
      xmlns:com="${com}">
      <com:BillingPointOfSaleInfo OriginApplication="${escapeXml(originApplication)}" />
      <com:TicketNumber>${escapeXml(ticketNumber)}</com:TicketNumber>${locatorCodeXml}
    </air:AirRetrieveDocumentReq>
  </s:Body>
</s:Envelope>`;
};

// ─────────────────────────────────────────────────────────────────────────────
// STEP 2 — MAKE HTTPS SOAP REQUEST
// [#3]  Connection: keep-alive
// [#4,#9] Accept-Encoding: gzip + zlib decompression
// [#11] Expect: '' — suppress 100-continue
// [#12] Content-Type: text/xml;charset=UTF-8
// ─────────────────────────────────────────────────────────────────────────────
const makeSoapRequest = (xmlBody, requestId) => {
  return new Promise((resolve, reject) => {
    const urlObj    = new URL(TRAVELPORT_CONFIG.soapUrl);
    const isHttps   = urlObj.protocol === 'https:';
    const transport = isHttps ? https : http;

    const authHeader = Buffer.from(
      `${TRAVELPORT_CONFIG.username}:${TRAVELPORT_CONFIG.password}`
    ).toString('base64');

    const bodyBuffer = Buffer.from(xmlBody, 'utf8');

    const options = {
      hostname: urlObj.hostname,
      port:     urlObj.port || (isHttps ? 443 : 80),
      path:     urlObj.pathname + (urlObj.search || ''),
      method:   'POST',
      agent:    httpsAgent,
      headers: {
        'Content-Type':    'text/xml;charset=UTF-8',
        'Expect':          '',
        'Connection':      'keep-alive',
        'Accept-Encoding': 'gzip, deflate',
        'Authorization':   `Basic ${authHeader}`,
        'Content-Length':  bodyBuffer.length,
        'SOAPAction':      '',
        'Accept':          'text/xml, application/xml',
        'User-Agent':      'NodeJS-Travelport-uAPI-Client/1.0',
      },
    };

    console.log(`[${requestId}]   [HTTP] Endpoint         : ${TRAVELPORT_CONFIG.soapUrl}`);
    console.log(`[${requestId}]   [HTTP] Content-Type     : text/xml;charset=UTF-8  [#12]`);
    console.log(`[${requestId}]   [HTTP] Content-Length   : ${bodyBuffer.length} bytes`);
    console.log(`[${requestId}]   [HTTP] Connection       : keep-alive  [#3]`);
    console.log(`[${requestId}]   [HTTP] Accept-Encoding  : gzip, deflate  [#4 & #9]`);
    console.log(`[${requestId}]   [HTTP] Expect           : (suppressed)  [#11]`);
    console.log(`[${requestId}]   [HTTP] Agent keepAlive  : true | 512KB buffer  [#8 & #10]`);
    console.log(`[${requestId}]   [HTTP] Timeout          : ${TRAVELPORT_CONFIG.timeoutMs}ms`);

    const req = transport.request(options, (res) => {
      const chunks     = [];
      let   totalBytes = 0;

      console.log(`[${requestId}]   [HTTP] Response Status  : ${res.statusCode} ${res.statusMessage}`);
      console.log(`[${requestId}]   [HTTP] Response Headers : ${JSON.stringify(res.headers, null, 2)}`);

      const contentEncoding = (res.headers['content-encoding'] || '').toLowerCase();
      console.log(`[${requestId}]   [HTTP] Content-Encoding : ${contentEncoding || '(none)'}`);

      res.on('data',  (chunk) => { chunks.push(chunk); totalBytes += chunk.length; });

      res.on('end', () => {
        const rawBuffer = Buffer.concat(chunks);
        console.log(`[${requestId}]   [HTTP] Bytes received   : ${totalBytes}`);

        if (contentEncoding === 'gzip') {
          console.log(`[${requestId}]   [GZIP] Decompressing...`);
          zlib.gunzip(rawBuffer, (err, dec) => {
            if (err) return reject(new Error(`Gzip decompression failed: ${err.message}`));
            const rawXml = dec.toString('utf8');
            console.log(`[${requestId}]   [GZIP] ✓ Decompressed: ${rawXml.length} chars`);
            resolve({ statusCode: res.statusCode, rawXml });
          });
        } else if (contentEncoding === 'deflate') {
          zlib.inflate(rawBuffer, (err, dec) => {
            if (err) return reject(new Error(`Deflate decompression failed: ${err.message}`));
            const rawXml = dec.toString('utf8');
            console.log(`[${requestId}]   [DEFLATE] ✓ Decompressed: ${rawXml.length} chars`);
            resolve({ statusCode: res.statusCode, rawXml });
          });
        } else {
          const rawXml = rawBuffer.toString('utf8');
          console.log(`[${requestId}]   [HTTP] Uncompressed: ${rawXml.length} chars`);
          resolve({ statusCode: res.statusCode, rawXml });
        }
      });

      res.on('error', (err) => reject(err));
    });

    req.setTimeout(TRAVELPORT_CONFIG.timeoutMs, () => {
      req.destroy();
      const e = new Error(`Request timed out after ${TRAVELPORT_CONFIG.timeoutMs}ms`);
      e.code = 'ETIMEDOUT';
      reject(e);
    });

    req.on('error', (err) => {
      console.error(`[${requestId}]   [HTTP] ✗ Request error: ${err.message}`);
      reject(err);
    });

    req.write(bodyBuffer);
    req.end();
  });
};

// ─────────────────────────────────────────────────────────────────────────────
// STEP 3 — PARSE SOAP XML → JSON using xml2js
// ─────────────────────────────────────────────────────────────────────────────
const parseSoapResponse = async (rawXml, requestId) => {
  console.log(`[${requestId}]   [PARSE] Parsing with xml2js...`);

  const parser = new xml2js.Parser({
    explicitArray:      false,
    ignoreAttrs:        false,
    mergeAttrs:         true,
    tagNameProcessors:  [xml2js.processors.stripPrefix],
    attrNameProcessors: [xml2js.processors.stripPrefix],
    valueProcessors:     [xml2js.processors.parseNumbers, xml2js.processors.parseBooleans],
    attrValueProcessors: [xml2js.processors.parseNumbers, xml2js.processors.parseBooleans],
  });

  try {
    const parsed = await parser.parseStringPromise(rawXml);
    console.log(`[${requestId}]   [PARSE] ✓ Parsed successfully.`);
    return parsed;
  } catch (parseError) {
    console.error(`[${requestId}]   [PARSE] ✗ ${parseError.message}`);
    throw new Error(`Failed to parse SOAP XML: ${parseError.message}`);
  }
};

// ─────────────────────────────────────────────────────────────────────────────
// STEP 4 — CHECK FOR SOAP FAULT
// ─────────────────────────────────────────────────────────────────────────────
const checkForSoapFault = (parsed, requestId) => {
  const body  = parsed?.Envelope?.Body;
  if (!body) throw new Error('Unexpected SOAP response: missing Body element.');

  const fault = body?.Fault;
  if (fault) {
    console.error(`[${requestId}]   [FAULT] ✗ SOAP Fault from Travelport`);
    console.error(`[${requestId}]   [FAULT]   faultcode   : ${fault.faultcode}`);
    console.error(`[${requestId}]   [FAULT]   faultstring : ${fault.faultstring}`);
    console.error(`[${requestId}]   [FAULT]   detail      : ${JSON.stringify(fault.detail)}`);

    const err       = new Error(`SOAP Fault: ${fault.faultstring}`);
    err.isSoapFault = true;
    err.faultCode   = fault.faultcode   || 'Unknown';
    err.faultString = fault.faultstring || 'Unknown error from Travelport';
    err.faultDetail = fault.detail      || null;
    throw err;
  }

  console.log(`[${requestId}]   [FAULT] ✓ No SOAP Fault.`);
};

// ─────────────────────────────────────────────────────────────────────────────
// STEP 5 — EXTRACT AND NORMALIZE AirRetrieveDocumentRsp
//
// _raw is logged here for Travelport certification (they verify raw responses).
// It is included in the return object so the controller can log it, then the
// controller strips it before sending the JSON response to the client.
// Result this way: logs have full raw data, API response has clean data only.
// ─────────────────────────────────────────────────────────────────────────────
const extractDocumentResponse = (parsed, requestId) => {
  const body = parsed?.Envelope?.Body;
  const rsp  = body?.AirRetrieveDocumentRsp;

  if (!rsp) {
    console.error(`[${requestId}]   [EXTRACT] ✗ AirRetrieveDocumentRsp not found.`);
    console.error(`[${requestId}]   [EXTRACT]   Body keys: ${Object.keys(body || {}).join(', ')}`);
    throw new Error('AirRetrieveDocumentRsp not found in Travelport response.');
  }

  console.log(`[${requestId}]   [EXTRACT] ✓ AirRetrieveDocumentRsp found.`);

  // Normalize ETR → always an array (Travelport sends object for 1, array for many)
  let eTickets = rsp?.ETR || null;
  if (eTickets && !Array.isArray(eTickets)) {
    eTickets = [eTickets];
    console.log(`[${requestId}]   [EXTRACT] ETR: normalized single object → array[1]`);
  } else if (Array.isArray(eTickets)) {
    console.log(`[${requestId}]   [EXTRACT] ETR: array of ${eTickets.length} ticket(s)`);
  } else {
    console.log(`[${requestId}]   [EXTRACT] ETR: none found`);
  }

  return {
    transactionId:            rsp.TransactionId            || null,
    traceId:                  rsp.TraceId                  || null,
    universalRecordLocator:   rsp.UniversalRecordLocatorCode || null,
    responseTimeMs:           rsp.ResponseTime             || null,
    eTickets:                 eTickets                     || [],
    // _raw: included so controller can log it, then strips before HTTP response
    // This avoids duplicate data reaching the client.
    _raw: rsp,
  };
};

// ─────────────────────────────────────────────────────────────────────────────
// MAIN EXPORTED FUNCTION
// ─────────────────────────────────────────────────────────────────────────────
const retrieveDocument = async (payload, requestId) => {
  console.log(`\n[${requestId}] ┌─ AirRetrieveDocumentService START ─────────────────────`);
  console.log(`[${requestId}] │ Schema  : ${SCHEMA_VERSION}  [#1 & #2]`);
  console.log(`[${requestId}] │ Pool    : keepAlive=true  [#3 & #8]`);
  console.log(`[${requestId}] │ Gzip    : gzip/deflate  [#4 & #9]`);
  console.log(`[${requestId}] │ Buffer  : 512KB  [#10]`);
  console.log(`[${requestId}] │ Expect  : suppressed  [#11]`);

  console.log(`[${requestId}] │ STEP 1/5 → Building SOAP XML...`);
  const soapXml = buildSoapXml(payload, requestId);
  console.log(`[${requestId}] │ ✓ XML built (${soapXml.length} chars)`);
  console.log(`[${requestId}] │ ─── SOAP XML ───────────────────────────────────────────`);
  console.log(soapXml);
  console.log(`[${requestId}] │ ────────────────────────────────────────────────────────`);

  console.log(`[${requestId}] │ STEP 2/5 → Calling Travelport...`);
  const t0 = Date.now();
  const { statusCode, rawXml } = await makeSoapRequest(soapXml, requestId);
  console.log(`[${requestId}] │ ✓ Response in ${Date.now() - t0}ms | HTTP ${statusCode}`);
  console.log(`[${requestId}] │ ─── RAW SOAP RESPONSE ──────────────────────────────────`);
  console.log(rawXml);
  console.log(`[${requestId}] │ ────────────────────────────────────────────────────────`);

  console.log(`[${requestId}] │ STEP 3/5 → Parsing XML...`);
  const parsed = await parseSoapResponse(rawXml, requestId);
  console.log(`[${requestId}] │ ✓ Parsed JSON:`);
  console.log(JSON.stringify(parsed, null, 2));

  console.log(`[${requestId}] │ STEP 4/5 → Checking for SOAP Fault...`);
  checkForSoapFault(parsed, requestId);

  console.log(`[${requestId}] │ STEP 5/5 → Extracting AirRetrieveDocumentRsp...`);
  const result = extractDocumentResponse(parsed, requestId);
  console.log(`[${requestId}] │ ✓ Done.`);
  console.log(`[${requestId}] └─ AirRetrieveDocumentService END ───────────────────────\n`);

  return result;
};

module.exports = { retrieveDocument };