import "server-only";

import { readFileSync, statSync } from "node:fs";
import path from "node:path";

import { Reader, type CountryResponse } from "maxmind";

import { getRequestClientIp } from "@/lib/request-client-ip";

const COUNTRY_HEADERS = [
  "cf-ipcountry",
  "x-vercel-ip-country",
  "cloudfront-viewer-country",
  "x-country-code",
] as const;

const DEFAULT_GEOIP_DATABASE_PATH =
  "/var/www/front/whatip/GeoLite2-Country.mmdb";
const READER_RECHECK_INTERVAL_MS = 60_000;

type CountrySource = "geoip" | "header" | null;

type GeoIpCache = {
  databasePath: string | null;
  modifiedAt: number;
  nextCheckAt: number;
  reader: Reader<CountryResponse> | null;
};

const geoIpCache: GeoIpCache = {
  databasePath: null,
  modifiedAt: 0,
  nextCheckAt: 0,
  reader: null,
};

function normalizeCountryCode(value: unknown) {
  if (typeof value !== "string") return null;
  const countryCode = value.trim().toUpperCase();
  return /^[A-Z]{2}$/.test(countryCode) && countryCode !== "XX"
    ? countryCode
    : null;
}

function findGeoIpDatabase() {
  const configuredPath = process.env.OFFICIAL_MAIL_GEOIP_DATABASE_PATH?.trim();
  const candidates = [
    configuredPath,
    DEFAULT_GEOIP_DATABASE_PATH,
    path.join(process.cwd(), "GeoLite2-Country.mmdb"),
    path.join(process.cwd(), "data", "GeoLite2-Country.mmdb"),
  ].filter((candidate): candidate is string => Boolean(candidate));

  for (const candidate of new Set(candidates)) {
    try {
      const stats = statSync(candidate);
      if (stats.isFile()) return { databasePath: candidate, modifiedAt: stats.mtimeMs };
    } catch {
      // Try the next configured location.
    }
  }

  return null;
}

function getGeoIpReader() {
  const now = Date.now();
  if (now < geoIpCache.nextCheckAt) return geoIpCache.reader;
  geoIpCache.nextCheckAt = now + READER_RECHECK_INTERVAL_MS;

  const database = findGeoIpDatabase();
  if (!database) {
    geoIpCache.databasePath = null;
    geoIpCache.modifiedAt = 0;
    geoIpCache.reader = null;
    return null;
  }

  if (
    geoIpCache.reader &&
    geoIpCache.databasePath === database.databasePath &&
    geoIpCache.modifiedAt === database.modifiedAt
  ) {
    return geoIpCache.reader;
  }

  try {
    geoIpCache.reader = new Reader<CountryResponse>(
      readFileSync(database.databasePath),
    );
    geoIpCache.databasePath = database.databasePath;
    geoIpCache.modifiedAt = database.modifiedAt;
  } catch (error) {
    console.error("Unable to load the GeoIP country database", error);
    geoIpCache.databasePath = null;
    geoIpCache.modifiedAt = 0;
    geoIpCache.reader = null;
  }

  return geoIpCache.reader;
}

function getCountryFromGeoIp(ipAddress: string | null) {
  if (!ipAddress) return null;

  try {
    const record = getGeoIpReader()?.get(ipAddress);
    return normalizeCountryCode(
      record?.country?.iso_code ?? record?.registered_country?.iso_code,
    );
  } catch {
    return null;
  }
}

function getCountryFromHeaders(requestHeaders: Pick<Headers, "get">) {
  for (const headerName of COUNTRY_HEADERS) {
    const countryCode = normalizeCountryCode(requestHeaders.get(headerName));
    if (countryCode) return countryCode;
  }

  return null;
}

export function resolveRequestCountry(
  requestHeaders: Pick<Headers, "get">,
): { countryCode: string | null; source: CountrySource } {
  const clientIp = getRequestClientIp(requestHeaders);
  const geoIpCountry = getCountryFromGeoIp(clientIp);
  if (geoIpCountry) return { countryCode: geoIpCountry, source: "geoip" };

  // Direct-origin traffic has a trusted X-Real-IP. Only accept externally
  // supplied country headers when there is no public client IP, unless an
  // upstream provider has explicitly been marked as trusted.
  if (
    !clientIp ||
    process.env.OFFICIAL_MAIL_TRUST_COUNTRY_HEADERS === "true"
  ) {
    const headerCountry = getCountryFromHeaders(requestHeaders);
    if (headerCountry) return { countryCode: headerCountry, source: "header" };
  }

  return { countryCode: null, source: null };
}

export function getRequestCountryCode(
  requestHeaders: Pick<Headers, "get">,
) {
  return resolveRequestCountry(requestHeaders).countryCode;
}
