import type { Request } from 'express';

const INTERNAL_HOSTNAMES = new Set([
  'api-gateway',
  'sso-core',
  'frontend',
  'docs',
  'media-ws',
  'minio',
  'postgres',
  'redis',
  'rabbitmq',
  'ldap-auth'
]);

export function normalizePublicBaseUrl(url: string) {
  return url.trim().replace(/\/+$/, '');
}

/**
 * На выделенном API-домене (api.example.com) gateway слушает в корне (/fedcm, /oauth).
 * Префикс /idp-api нужен только на SSO-домене. Убираем лишний суффикс из PUBLIC_API_URL.
 */
export function normalizeDedicatedApiIssuer(url: string) {
  const normalized = normalizePublicBaseUrl(url);
  try {
    const parsed = new URL(normalized);
    const host = parsed.hostname.toLowerCase();
    const path = parsed.pathname.replace(/\/+$/, '') || '/';
    if (host.startsWith('api.') && (path === '/idp-api' || path.endsWith('/idp-api'))) {
      return `${parsed.protocol}//${parsed.host}`;
    }
  } catch {
    return normalized;
  }
  return normalized;
}

export function isInternalHostname(hostname: string) {
  const host = hostname.trim().toLowerCase();
  if (!host) {
    return true;
  }

  if (INTERNAL_HOSTNAMES.has(host)) {
    return true;
  }

  if (/^\d+\.\d+\.\d+\.\d+$/.test(host) || host === 'localhost') {
    return false;
  }

  // Одно слово без точки — типичное имя Docker-сервиса (api-gateway, sso-core).
  return !host.includes('.');
}

export function isBrowserReachableBaseUrl(url: string) {
  try {
    const parsed = new URL(url);
    if (!['http:', 'https:'].includes(parsed.protocol)) {
      return false;
    }
    return !isInternalHostname(parsed.hostname);
  } catch {
    return false;
  }
}

/** Абсолютный HTTPS (или localhost HTTP) URL для FedCM login_url / configUrl. */
export function isValidFedcmEndpointUrl(url: string) {
  try {
    const parsed = new URL(url);
    if (!['http:', 'https:'].includes(parsed.protocol)) {
      return false;
    }
    if (isInternalHostname(parsed.hostname)) {
      return false;
    }
    if (isLocalDevBaseUrl(url)) {
      return parsed.protocol === 'http:' || parsed.protocol === 'https:';
    }
    return parsed.protocol === 'https:';
  } catch {
    return false;
  }
}

export function readRequestHost(req?: Request): string | undefined {
  if (!req) {
    return undefined;
  }

  const forwarded = req.headers['x-forwarded-host'];
  const host = (Array.isArray(forwarded) ? forwarded[0] : forwarded) ?? req.headers.host;
  return host?.split(',')[0]?.trim() || undefined;
}

export function readRequestProto(req?: Request): string {
  if (!req) {
    return 'https';
  }

  const forwarded = req.headers['x-forwarded-proto'];
  const proto = (Array.isArray(forwarded) ? forwarded[0] : forwarded) ?? req.protocol;
  return proto?.split(',')[0]?.trim() || 'https';
}

export function resolvePublicOriginFromRequest(req?: Request): string | null {
  const host = readRequestHost(req);
  if (!host) {
    return null;
  }

  const hostname = host.split(':')[0];
  if (isInternalHostname(hostname)) {
    return null;
  }

  return normalizePublicBaseUrl(`${readRequestProto(req)}://${host}`);
}

export function resolvePublicApiBaseFromRequest(req?: Request): string | null {
  const origin = resolvePublicOriginFromRequest(req);
  if (!origin) {
    return null;
  }

  return `${origin}/idp-api`;
}

export function resolvePublicFrontendBaseFromRequest(req?: Request): string | null {
  return resolvePublicOriginFromRequest(req);
}

/**
 * Возвращает канонический публичный base-URL для FedCM.
 *
 * FedCM требует, чтобы well-known файл (всегда запрашивается с eTLD+1, например
 * idpmvk.lpr) и discover.json (запрашивается с поддомена sso.idpmvk.lpr)
 * возвращали ОДИН и тот же configUrl. Поэтому здесь приоритет всегда у значения
 * из настроек (PUBLIC_API_URL / PUBLIC_FRONTEND_URL), а на хост запроса
 * переключаемся только если в настройках указан внутренний Docker-хост.
 */
export function isLocalDevHostname(hostname: string) {
  const host = hostname.trim().toLowerCase();
  return host === 'localhost' || host === '127.0.0.1' || host === '[::1]';
}

export function isLocalDevBaseUrl(url: string) {
  try {
    return isLocalDevHostname(new URL(url).hostname);
  } catch {
    return false;
  }
}

/** Выбирает публичный URL: приоритет у реального домена, не localhost из .env/seed. */
export function pickBestPublicBase(candidates: Array<string | null | undefined>, fallback: string) {
  const normalized = candidates
    .map((candidate) => candidate?.trim())
    .filter((candidate): candidate is string => Boolean(candidate))
    .map((candidate) => normalizePublicBaseUrl(candidate));

  const productionReachable = normalized.find(
    (candidate) => isBrowserReachableBaseUrl(candidate) && !isLocalDevBaseUrl(candidate)
  );
  if (productionReachable) {
    return productionReachable;
  }

  const reachable = normalized.find((candidate) => isBrowserReachableBaseUrl(candidate));
  if (reachable) {
    return reachable;
  }

  if (normalized[0]) {
    return normalized[0];
  }

  return normalizePublicBaseUrl(fallback);
}

export function preferCanonicalBase(stored: string, fromRequest: string | null) {
  return pickBestPublicBase([stored, fromRequest], stored || fromRequest || '');
}

export function preferBrowserReachableBase(stored: string, fromRequest: string | null) {
  if (fromRequest && isBrowserReachableBaseUrl(fromRequest)) {
    try {
      const storedUrl = new URL(stored);
      if (isInternalHostname(storedUrl.hostname)) {
        return fromRequest;
      }
    } catch {
      return fromRequest;
    }

    if (normalizePublicBaseUrl(stored) !== normalizePublicBaseUrl(fromRequest)) {
      return fromRequest;
    }
  }

  return stored;
}

export function hostsMatch(a: string, b: string) {
  if (a === b) {
    return true;
  }

  return a === 'localhost' && b === 'localhost';
}

/** eTLD+1 для FedCM (sso.idpmvk.lpr → idpmvk.lpr). Совпадает с registrable_domain в install.sh */
export function resolveRegistrableDomain(hostname: string): string {
  const host = hostname.trim().toLowerCase();
  if (!host) return host;
  const labels = host.split('.').filter(Boolean);
  if (labels.length <= 2) return host;
  return `${labels[labels.length - 2]}.${labels[labels.length - 1]}`;
}

export function resolveFedcmWebIdentityOrigin(issuer: string, frontendUrl: string): string {
  for (const candidate of [issuer, frontendUrl]) {
    try {
      const hostname = new URL(candidate).hostname;
      const apex = resolveRegistrableDomain(hostname);
      const proto = new URL(candidate).protocol;
      return `${proto}//${apex}`;
    } catch {
      // try next
    }
  }
  return frontendUrl;
}
