import { timingSafeEqual } from "crypto";

import createMiddleware from "next-intl/middleware";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

import { appLocales, createI18nRouting } from "./i18n/routing";
import {
  fromDevelopmentAdminPath,
  getAdminBaseUrl,
  getRequestHostname,
  isDevelopmentAdminPath,
  isAdminHost,
  hasDedicatedAdminHost,
  isInternalAdminPath,
  isLegacyAdminPath,
  toInternalAdminPath,
} from "./lib/admin-routing";
import {
  FALLBACK_LOCALE,
  getLocalizedPathWithDefault,
  isSupportedLocale,
  stripLocalePrefix,
} from "./lib/locale";

const ADMIN_SESSION_COOKIE = "moh_admin_session";

type SiteRuntimeState = {
  defaultLocale: (typeof appLocales)[number];
  maintenanceEnabled: boolean;
};

let runtimeStateCache: { value: SiteRuntimeState; expiresAt: number } | null = null;
const RUNTIME_STATE_CACHE_TTL_MS = 5_000;

function getSiteRuntimeStateOrigin(request: NextRequest): string {
  const configuredOrigin = process.env.SITE_RUNTIME_ORIGIN?.trim();

  if (configuredOrigin) {
    return configuredOrigin;
  }

  if (process.env.NODE_ENV === "production") {
    return "http://127.0.0.1:3000";
  }

  return request.nextUrl.origin;
}

function getPathLocale(pathname: string, fallbackLocale: (typeof appLocales)[number]) {
  const locale = pathname.split("/")[1];

  return isSupportedLocale(locale) ? locale : fallbackLocale;
}

function isComingSoonPath(pathname: string) {
  return stripLocalePrefix(pathname) === "/coming-soon";
}

async function getSiteRuntimeState(request: NextRequest): Promise<SiteRuntimeState> {
  const now = Date.now();

  if (runtimeStateCache !== null && runtimeStateCache.expiresAt > now) {
    return runtimeStateCache.value;
  }

  try {
    const runtimeStateUrl = new URL("/api/site/default-locale", getSiteRuntimeStateOrigin(request));
    const response = await fetch(runtimeStateUrl, {
      headers: {
        "x-middleware-request": "1",
      },
      cache: "no-store",
    });

    if (!response.ok) {
      return {
        defaultLocale: FALLBACK_LOCALE,
        maintenanceEnabled: false,
      };
    }

    const data = await response.json() as {
      defaultLocale?: string;
      maintenanceEnabled?: boolean;
    };

    const value: SiteRuntimeState = {
      defaultLocale: isSupportedLocale(data.defaultLocale) ? data.defaultLocale : FALLBACK_LOCALE,
      maintenanceEnabled: data.maintenanceEnabled === true,
    };

    runtimeStateCache = { value, expiresAt: now + RUNTIME_STATE_CACHE_TTL_MS };

    return value;
  } catch {
    return {
      defaultLocale: FALLBACK_LOCALE,
      maintenanceEnabled: false,
    };
  }
}

function getAdminBasicAuthUser(): string {
  return process.env.ADMIN_BASIC_AUTH_USER ?? "";
}

function getAdminBasicAuthPass(): string {
  return process.env.ADMIN_BASIC_AUTH_PASS ?? "";
}

function isAdminBasicAuthConfigured(): boolean {
  return Boolean(getAdminBasicAuthUser() && getAdminBasicAuthPass());
}

function timingSafeStringEqual(a: string, b: string): boolean {
  const left = Buffer.from(a);
  const right = Buffer.from(b);

  // Buffers of different length must still be compared against something of
  // equal length so the comparison time doesn't leak the expected length.
  if (left.length !== right.length) {
    timingSafeEqual(left, left);
    return false;
  }

  return timingSafeEqual(left, right);
}

function isAdminBasicAuthValid(request: NextRequest): boolean {
  if (!isAdminBasicAuthConfigured()) {
    return false;
  }

  const header = request.headers.get("authorization");
  if (!header || !header.startsWith("Basic ")) {
    return false;
  }

  try {
    const decoded = atob(header.slice(6));
    const index = decoded.indexOf(":");
    if (index === -1) {
      return false;
    }

    const user = decoded.slice(0, index);
    const pass = decoded.slice(index + 1);

    const userValid = timingSafeStringEqual(user, getAdminBasicAuthUser());
    const passValid = timingSafeStringEqual(pass, getAdminBasicAuthPass());

    return userValid && passValid;
  } catch {
    return false;
  }
}

export default async function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;
  const isDevelopmentAdminRequest =
    process.env.NODE_ENV !== "production" && isDevelopmentAdminPath(pathname);
  const hostname = getRequestHostname(
    request.headers.get("x-forwarded-host"),
    request.headers.get("host"),
    request.nextUrl.hostname,
  );
  const isAdminRequest = isDevelopmentAdminRequest || isAdminHost(hostname);
  const hasDedicatedAdminHostname = hasDedicatedAdminHost();
  const adminRobotsHeaders = {
    "X-Robots-Tag": "noindex, nofollow, noarchive, nosnippet, noimageindex",
  };

  if (
    isDevelopmentAdminRequest &&
    hasDedicatedAdminHostname &&
    !isAdminHost(hostname)
  ) {
    const redirectUrl = new URL(getAdminBaseUrl());
    redirectUrl.pathname = fromDevelopmentAdminPath(pathname);
    redirectUrl.search = request.nextUrl.search;
    return NextResponse.redirect(redirectUrl, 308);
  }

  if (isLegacyAdminPath(pathname) && process.env.NODE_ENV === "production") {
    return new NextResponse("Not Found", {
      status: 404,
    });
  }

  if (isInternalAdminPath(pathname) && process.env.NODE_ENV === "production" && !isAdminRequest) {
    return new NextResponse("Not Found", {
      status: 404,
    });
  }

  if (isAdminRequest) {
    if (isAdminBasicAuthConfigured() && !isAdminBasicAuthValid(request)) {
      return new NextResponse("Authentication required", {
        status: 401,
        headers: {
          "WWW-Authenticate": 'Basic realm="Admin Area", charset="UTF-8"',
          ...adminRobotsHeaders,
        },
      });
    }

    const rewriteUrl = request.nextUrl.clone();
    rewriteUrl.pathname = toInternalAdminPath(
      isDevelopmentAdminRequest ? fromDevelopmentAdminPath(pathname) : pathname,
    );
    const response = NextResponse.rewrite(rewriteUrl);
    response.headers.set("X-Robots-Tag", adminRobotsHeaders["X-Robots-Tag"]);
    return response;
  }

  const siteRuntimeState = await getSiteRuntimeState(request);
  const configuredDefaultLocale = siteRuntimeState.defaultLocale;
  const intlMiddleware = createMiddleware(createI18nRouting(configuredDefaultLocale));

  if (
    siteRuntimeState.maintenanceEnabled &&
    !request.cookies.has(ADMIN_SESSION_COOKIE) &&
    !isComingSoonPath(pathname)
  ) {
    const locale = getPathLocale(pathname, configuredDefaultLocale);
    const redirectUrl = request.nextUrl.clone();
    redirectUrl.pathname = getLocalizedPathWithDefault(locale, "/coming-soon", configuredDefaultLocale);
    return NextResponse.redirect(redirectUrl, 307);
  }

  return intlMiddleware(request);
}

export const config = {
  matcher: ["/((?!api|trpc|_next|_vercel|.*\\..*).*)"],
};
