import type { FastifyRequest } from 'fastify';

export type RequestAuditContext = {
  clientIp: string | null;
  externalIp: string | null;
  forwardedFor: string | null;
  realIp: string | null;
  host: string | null;
  origin: string | null;
  referer: string | null;
  userAgent: string | null;
  clientId: string | null;
};

function normalizeText(value: unknown) {
  return typeof value === 'string' ? value.trim() : '';
}

function normalizeHeaderValue(value: unknown) {
  if (Array.isArray(value)) {
    return normalizeText(value[0]);
  }

  return normalizeText(value);
}

function splitForwardedFor(value: string) {
  return value
    .split(',')
    .map((item) => item.trim())
    .filter(Boolean);
}

function stripIpDecorations(value: string) {
  const normalized = value.replace(/^for=/iu, '').replace(/^"|"$/g, '').trim();
  if (normalized.startsWith('[') && normalized.includes(']')) {
    return normalized.slice(1, normalized.indexOf(']')).trim();
  }
  const ipv4PortMatch = normalized.match(/^(\d{1,3}(?:\.\d{1,3}){3}):\d+$/u);
  if (ipv4PortMatch) {
    return ipv4PortMatch[1] ?? normalized;
  }
  return normalized;
}

function isPrivateOrLocalIp(value: string) {
  const normalized = stripIpDecorations(value).toLowerCase();
  if (!normalized) {
    return true;
  }

  if (normalized === '::1' || normalized === 'localhost') {
    return true;
  }

  if (normalized.startsWith('127.') || normalized.startsWith('10.') || normalized.startsWith('192.168.')) {
    return true;
  }

  if (/^172\.(1[6-9]|2\d|3[0-1])\./u.test(normalized)) {
    return true;
  }

  if (normalized.startsWith('fc') || normalized.startsWith('fd') || normalized.startsWith('fe80:')) {
    return true;
  }

  if (normalized.startsWith('::ffff:127.') || normalized.startsWith('::ffff:10.') || normalized.startsWith('::ffff:192.168.')) {
    return true;
  }

  if (/^::ffff:172\.(1[6-9]|2\d|3[0-1])\./u.test(normalized)) {
    return true;
  }

  return false;
}

function resolveExternalIp(candidates: string[]) {
  const cleaned = candidates.map((item) => stripIpDecorations(item)).filter(Boolean);
  return cleaned.find((item) => !isPrivateOrLocalIp(item)) ?? cleaned[0] ?? null;
}

export function extractRequestAuditContext(request: FastifyRequest): RequestAuditContext {
  const forwardedFor = normalizeHeaderValue(request.headers['x-forwarded-for']);
  const realIp = normalizeHeaderValue(request.headers['x-real-ip']) || normalizeHeaderValue(request.headers['cf-connecting-ip']);
  const clientIp = normalizeText(request.ip) || normalizeText(request.raw.socket.remoteAddress) || null;

  return {
    clientIp: clientIp ? stripIpDecorations(clientIp) : null,
    externalIp: resolveExternalIp([
      ...splitForwardedFor(forwardedFor),
      realIp,
      normalizeText(request.headers['x-client-ip']),
      clientIp ?? '',
    ]),
    forwardedFor: forwardedFor || null,
    realIp: realIp ? stripIpDecorations(realIp) : null,
    host: normalizeHeaderValue(request.headers.host) || null,
    origin: normalizeHeaderValue(request.headers.origin) || null,
    referer: normalizeHeaderValue(request.headers.referer) || null,
    userAgent: normalizeHeaderValue(request.headers['user-agent']) || null,
    clientId: normalizeHeaderValue(request.headers['x-client-id']) || null,
  };
}
