import { Dispatcher, ProxyAgent, Pool, request as undiciRequest } from 'undici';
import { config } from '../config';

let _mainPool: Pool | null = null;
let _mediaPool: Pool | null = null;
let _staticPool: Pool | null = null;
let _fastendPool: Pool | null = null;
let _proxyPool: ProxyAgent[] | null = null;
let _proxyIdx = 0;

function getMainPool(): Pool {
  if (!_mainPool) {
    _mainPool = new Pool(config.upstream.main, {
      connections: 50,
      pipelining: 1,
      connect: { rejectUnauthorized: true },
    });
  }
  return _mainPool;
}

function getMediaPool(): Pool {
  if (!_mediaPool) {
    _mediaPool = new Pool(config.upstream.media, {
      connections: 50,
      pipelining: 1,
      connect: { rejectUnauthorized: true },
    });
  }
  return _mediaPool;
}

function getStaticPool(): Pool {
  if (!_staticPool) {
    _staticPool = new Pool(config.upstream.static, {
      connections: 50,
      pipelining: 1,
      connect: { rejectUnauthorized: true },
    });
  }
  return _staticPool;
}

function getFastendPool(): Pool {
  if (!_fastendPool) {
    _fastendPool = new Pool(config.upstream.fastend, {
      connections: 25,
      pipelining: 1,
      connect: { rejectUnauthorized: true },
    });
  }
  return _fastendPool;
}

function getProxyAgent(): Dispatcher {
  if (config.proxyPool.enabled && config.proxyPool.urls.length > 0) {
    if (!_proxyPool) {
      _proxyPool = config.proxyPool.urls.map(
        (url) => new ProxyAgent({ uri: url, connections: 10 }),
      );
    }
    const agent = _proxyPool[_proxyIdx % _proxyPool.length];
    _proxyIdx++;
    return agent;
  }
  return getMainPool();
}

export interface UpstreamRequest {
  method: string;
  path: string;
  headers: Record<string, string>;
  body?: Buffer | null;
  target?: 'main' | 'media' | 'static' | 'fastend';
}

export interface UpstreamResponse {
  statusCode: number;
  headers: Record<string, string | string[]>;
  body: Buffer;
}

function getDispatcher(target: 'main' | 'media' | 'static' | 'fastend'): Dispatcher {
  // Datacenter IPs outside EU are geo-blocked by Akamai — route everything
  // through the proxy pool when enabled, not only HTML from www.sinsay.com.
  if (config.proxyPool.enabled && config.proxyPool.urls.length > 0) {
    return getProxyAgent();
  }
  switch (target) {
    case 'media':
      return getMediaPool();
    case 'static':
      return getStaticPool();
    case 'fastend':
      return getFastendPool();
    default:
      return getMainPool();
  }
}

export async function proxyUpstream(req: UpstreamRequest): Promise<UpstreamResponse> {
  const target = req.target ?? 'main';
  const dispatcher = getDispatcher(target);

  const origin =
    target === 'media'
      ? config.upstream.media
      : target === 'static'
        ? config.upstream.static
        : target === 'fastend'
          ? config.upstream.fastend
          : config.upstream.main;

  const { statusCode, headers, body } = await undiciRequest(
    `${origin}${req.path}`,
    {
      method: req.method as Dispatcher.HttpMethod,
      headers: req.headers,
      body: req.body ?? undefined,
      dispatcher,
      // undici auto-decompresses gzip/br
      throwOnError: false,
      maxRedirections: 5,
    },
  );

  const chunks: Buffer[] = [];
  for await (const chunk of body) {
    chunks.push(chunk instanceof Buffer ? chunk : Buffer.from(chunk));
  }
  const responseBody = Buffer.concat(chunks);

  return {
    statusCode,
    headers: headers as Record<string, string | string[]>,
    body: responseBody,
  };
}
