// Thin server-to-server HTTP client for CSM integration API.
// Always sends X-Integration-Key.
//
// KEEP-ALIVE DISABLED ON PURPOSE (2026-07-10): CSM (csm.vuphong.vn) is the
// crm-backend on the same box. When it restarts, any pooled keep-alive socket
// AOM holds goes half-open and the next request on it hangs until the timeout,
// causing intermittent login failures (browser sees ERR_CONNECTION_TIMED_OUT).
// A fresh socket per request (keepAlive:false) eliminates the stale-socket hang.
// Traffic here is low (auth verify + 45s directory cache), so the extra TLS
// handshake per call is negligible. NOTE: global fetch() ignores a
// `Connection: close` header (forbidden header name), which is why we use
// node:https with an explicit non-keep-alive Agent instead of fetch().
import http from 'node:http';
import https from 'node:https';
import { URL } from 'node:url';
import { CSM } from './config';

export class CsmError extends Error {
  status: number;
  constructor(message: string, status = 502) {
    super(message);
    this.name = 'CsmError';
    this.status = status;
  }
}

type CsmRequestInit = {
  method?: string;
  body?: string;
  headers?: Record<string, string>;
};

const csmHttpsAgent = new https.Agent({ keepAlive: false });
const csmHttpAgent = new http.Agent({ keepAlive: false });

function csmFetch(path: string, init: CsmRequestInit = {}): Promise<any> {
  const url = new URL(`${CSM.baseUrl()}${path}`);
  const isHttps = url.protocol === 'https:';
  const mod = isHttps ? https : http;
  const agent = isHttps ? csmHttpsAgent : csmHttpAgent;
  const bodyBuf = init.body != null ? Buffer.from(init.body, 'utf8') : null;
  const hostHeader = CSM.hostHeader();

  return new Promise((resolve, reject) => {
    let settled = false;
    // Hard overall deadline covering DNS + connect + response. req.setTimeout
    // alone only guards the idle socket phase, so a stalled connect could run
    // far past CSM.timeoutMs(); this timer bounds the whole call.
    const deadline = setTimeout(() => {
      if (settled) return;
      req.destroy(new CsmError('CSM request timed out', 504));
    }, CSM.timeoutMs());
    const finish = (fn: (v: any) => void, v: any) => {
      if (settled) return;
      settled = true;
      clearTimeout(deadline);
      fn(v);
    };

    const req = mod.request(
      url,
      {
        method: init.method || 'GET',
        agent,
        headers: {
          'Content-Type': 'application/json',
          'X-Integration-Key': CSM.key(),
          // Present the public host over loopback so CSM's HTTPS guard trusts us.
          ...(hostHeader ? { Host: hostHeader } : {}),
          ...(bodyBuf ? { 'Content-Length': String(bodyBuf.length) } : {}),
          ...(init.headers || {}),
        },
      },
      (res) => {
        const chunks: Buffer[] = [];
        res.on('data', (c: Buffer) => chunks.push(c));
        res.on('end', () => {
          const text = Buffer.concat(chunks).toString('utf8');
          let body: any = null;
          try {
            body = text ? JSON.parse(text) : null;
          } catch {
            body = text;
          }
          const status = res.statusCode || 0;
          if (status < 200 || status >= 300) {
            const msg = (body && typeof body === 'object' && (body.message || body.reason)) || `CSM HTTP ${status}`;
            finish(reject, new CsmError(String(msg), status));
            return;
          }
          finish(resolve, body);
        });
      },
    );

    req.on('error', (e: any) => {
      finish(reject, e instanceof CsmError ? e : new CsmError(`CSM unreachable: ${e?.message || e}`, 504));
    });

    if (bodyBuf) req.write(bodyBuf);
    req.end();
  });
}

/** Unwrap `{ success, data }` envelope if present; otherwise return body as-is. */
export function unwrap(body: any): any {
  if (body && typeof body === 'object' && 'success' in body && 'data' in body) {
    return body.data;
  }
  return body;
}

export function csmGet(path: string): Promise<any> {
  return csmFetch(path, { method: 'GET' });
}

export function csmPost(path: string, json: any): Promise<any> {
  return csmFetch(path, { method: 'POST', body: JSON.stringify(json) });
}
