/**
 * HTTP utilities - Fetch helpers and HTTP operations
 */
const http = require('http');
const https = require('https');
const { TIMEOUTS } = require('../utilities/constants');

// HTTPS agent that trusts internal CA certs (self-signed .sami TLD etc.)
// Lazy-initialized singleton to avoid creating a new agent per request.
let _internalHttpsAgent;
function getInternalHttpsAgent() {
  if (!_internalHttpsAgent) {
    _internalHttpsAgent = new https.Agent({ rejectUnauthorized: false });
  }
  return _internalHttpsAgent;
}

/**
 * Fetch with automatic timeout
 * Drop-in replacement for fetch() with AbortSignal timeout
 *
 * Handles two cases where native fetch() doesn't work:
 * 1. Caddy admin API (:2019) - rejects undici fetch
 * 2. HTTPS with self-signed certs (.sami TLD) - undici can't use Node's tls agent
 */
function fetchT(url, opts = {}, timeoutMs = TIMEOUTS.HTTP_DEFAULT) {
  // Caddy admin API rejects Node.js undici fetch - use raw http.request
  if (url.includes(':2019')) {
    return _httpFetch(url, opts, timeoutMs);
  }

  // HTTPS with self-signed certs: use raw https.request with rejectUnauthorized:false
  // Node.js native fetch() (undici) ignores the `agent` option and always validates certs.
  if (url.startsWith('https://')) {
    return _httpsFetch(url, opts, timeoutMs);
  }

  if (!opts.signal) {
    opts = { ...opts, signal: AbortSignal.timeout(timeoutMs) };
  }
  // The `timeout` key in fetch() opts is silently ignored by undici. Callers
  // should use the third arg of fetchT() (timeoutMs) instead. If a caller
  // passes `timeout: N` here, it's almost certainly a bug — we used to silently
  // strip it, which masked the issue. Now we surface it in logs and strip it.
  if ('timeout' in opts) {
    console.warn(`[fetchT] opts.timeout=${opts.timeout} is ignored — pass timeoutMs as the 3rd arg of fetchT() instead. Called from: ${new Error().stack.split('\n').slice(2, 4).join(' <- ')}`);
    const { timeout: _timeout, ...rest } = opts;
    opts = rest;
  }
  return fetch(url, opts);
}

/**
 * Raw https.request wrapper for self-signed cert support
 * Node.js native fetch() (undici) cannot be configured with rejectUnauthorized:false,
 * so we use the standard https module for internal HTTPS endpoints.
 */
function _httpsFetch(url, opts = {}, timeoutMs = TIMEOUTS.HTTP_DEFAULT) {
  return new Promise((resolve, reject) => {
    const parsed = new URL(url);
    const options = {
      hostname: parsed.hostname,
      port: parsed.port || 443,
      path: parsed.pathname + parsed.search,
      method: (opts.method || 'GET').toUpperCase(),
      headers: { ...opts.headers },
      timeout: timeoutMs,
      agent: getInternalHttpsAgent(),
    };

    if (opts.body && !options.headers['Content-Length']) {
      options.headers['Content-Length'] = Buffer.byteLength(opts.body);
    }

    const MAX_RESPONSE_SIZE = 10 * 1024 * 1024; // 10MB
    const req = https.request(options, (res) => {
      let data = '';
      let size = 0;

      res.on('data', chunk => {
        size += chunk.length;
        if (size > MAX_RESPONSE_SIZE) {
          res.destroy();
          reject(new Error(`Response from ${url} exceeded ${MAX_RESPONSE_SIZE} bytes`));
          return;
        }
        data += chunk;
      });

      res.on('end', () => {
        resolve({
          ok: res.statusCode >= 200 && res.statusCode < 300,
          status: res.statusCode,
          statusText: res.statusMessage,
          json: () => Promise.resolve(JSON.parse(data)),
          text: () => Promise.resolve(data),
          headers: {
            get: (k) => res.headers[k.toLowerCase()],
            getSetCookie: () => {
              const sc = res.headers['set-cookie'];
              if (!sc) return [];
              return Array.isArray(sc) ? sc : [sc];
            }
          },
        });
      });
    });

    req.on('timeout', () => {
      req.destroy();
      reject(new Error(`Request to ${url} timed out after ${timeoutMs}ms`));
    });
    req.on('error', reject);
    if (opts.body) req.write(opts.body);
    req.end();
  });
}

/**
 * Raw http.request wrapper for Caddy admin API
 */
function _httpFetch(url, opts = {}, timeoutMs = TIMEOUTS.HTTP_DEFAULT) {
  return new Promise((resolve, reject) => {
    const parsed = new URL(url);
    const options = {
      hostname: parsed.hostname,
      port: parsed.port || 2019,
      path: parsed.pathname + parsed.search,
      method: (opts.method || 'GET').toUpperCase(),
      headers: { ...opts.headers },
      timeout: timeoutMs,
    };
    
    if (opts.body) {
      options.headers['Content-Length'] = Buffer.byteLength(opts.body);
    }
    
    const MAX_RESPONSE_SIZE = 10 * 1024 * 1024; // 10MB
    const req = http.request(options, (res) => {
      let data = '';
      let size = 0;
      
      res.on('data', chunk => {
        size += chunk.length;
        if (size > MAX_RESPONSE_SIZE) {
          res.destroy();
          reject(new Error(`Response from ${url} exceeded ${MAX_RESPONSE_SIZE} bytes`));
          return;
        }
        data += chunk;
      });
      
      res.on('end', () => {
        resolve({
          ok: res.statusCode >= 200 && res.statusCode < 300,
          status: res.statusCode,
          statusText: res.statusMessage,
          json: () => Promise.resolve(JSON.parse(data)),
          text: () => Promise.resolve(data),
          headers: {
            get: (k) => res.headers[k.toLowerCase()],
            getSetCookie: () => {
              const sc = res.headers['set-cookie'];
              if (!sc) return [];
              return Array.isArray(sc) ? sc : [sc];
            }
          },
        });
      });
    });

    req.on('timeout', () => {
      req.destroy();
      reject(new Error(`Request to ${url} timed out after ${timeoutMs}ms`));
    });
    req.on('error', reject);
    if (opts.body) req.write(opts.body);
    req.end();
  });
}

module.exports = { fetchT };
