import { createHash, createHmac, timingSafeEqual } from "crypto";
import { cookies, headers } from "next/headers";
import { redirect } from "next/navigation";

import { prisma } from "./prisma";
import { getAdminAppPath } from "./admin-routing";

export const ADMIN_SESSION_COOKIE = "moh_admin_session";
const ADMIN_SESSION_VALUE = "superadmin";
const MAX_FAILED_ATTEMPTS = 5;
const LOCKOUT_SECONDS = 15 * 60;
const ADMIN_LOCKOUT_KEY_PREFIX = "admin_lockout";

function getSecret(): string {
  return process.env.ADMIN_AUTH_SECRET ?? "";
}

function getPassword(): string {
  return process.env.ADMIN_PASSWORD ?? "";
}

function parseHostname(value: string | undefined): string | undefined {
  if (!value) {
    return undefined;
  }

  const trimmed = value.trim().toLowerCase();

  if (!trimmed) {
    return undefined;
  }

  try {
    return new URL(trimmed).hostname.toLowerCase();
  } catch {
    return trimmed.replace(/^https?:\/\//, "").split("/")[0]?.replace(/:\d+$/, "") || undefined;
  }
}

function isCookieDomainCandidate(hostname: string | undefined): hostname is string {
  return Boolean(
    hostname &&
      hostname !== "localhost" &&
      !hostname.endsWith(".localhost") &&
      !/^\d{1,3}(\.\d{1,3}){3}$/.test(hostname) &&
      !hostname.includes(":"),
  );
}

function getSharedCookieHostname(hostnames: string[]): string | undefined {
  const validHostnames = hostnames.filter(isCookieDomainCandidate);

  if (validHostnames.length === 0) {
    return undefined;
  }

  if (validHostnames.length === 1) {
    return validHostnames[0];
  }

  const reversedSegments = validHostnames.map((hostname) => hostname.split(".").reverse());
  const sharedSegments: string[] = [];

  for (let index = 0; index < reversedSegments[0].length; index += 1) {
    const segment = reversedSegments[0][index];

    if (!segment || reversedSegments.some((parts) => parts[index] !== segment)) {
      break;
    }

    sharedSegments.push(segment);
  }

  if (sharedSegments.length < 2) {
    return validHostnames[0];
  }

  return sharedSegments.reverse().join(".");
}

function getAdminCookieDomain(): string | undefined {
  const hostname = getSharedCookieHostname([
    parseHostname(process.env.NEXT_PUBLIC_SITE_URL),
    parseHostname(process.env.NEXT_PUBLIC_ADMIN_URL),
    parseHostname(process.env.ADMIN_HOST),
  ].filter((value): value is string => Boolean(value)));

  return hostname ? `.${hostname}` : undefined;
}

function signValue(value: string): string {
  return createHmac("sha256", getSecret()).update(value).digest("hex");
}

function buildToken(): string {
  return `${ADMIN_SESSION_VALUE}.${signValue(ADMIN_SESSION_VALUE)}`;
}

function verifyToken(token: string): boolean {
  const parts = token.split(".");
  if (parts.length !== 2) {
    return false;
  }

  const [value, signature] = parts;
  if (value !== ADMIN_SESSION_VALUE) {
    return false;
  }

  const expected = signValue(value);
  const left = Buffer.from(signature);
  const right = Buffer.from(expected);

  if (left.length !== right.length) {
    return false;
  }

  return timingSafeEqual(left, right);
}

async function getClientIp(): Promise<string> {
  const requestHeaders = await headers();
  const forwardedFor = requestHeaders.get("x-forwarded-for");

  if (forwardedFor) {
    return forwardedFor.split(",")[0]?.trim() ?? "unknown";
  }

  return requestHeaders.get("x-real-ip")?.trim() ?? "unknown";
}

function getLockoutKey(ip: string): string {
  const hash = createHash("sha256").update(ip).digest("hex").slice(0, 16);
  return `${ADMIN_LOCKOUT_KEY_PREFIX}:${hash}`;
}

async function cleanupExpiredLockouts(): Promise<void> {
  try {
    const cutoff = new Date(Date.now() - LOCKOUT_SECONDS * 2 * 1000);
    await prisma.$executeRaw`
      DELETE FROM "AppConfig"
      WHERE key LIKE ${`${ADMIN_LOCKOUT_KEY_PREFIX}:%`}
      AND "updatedAt" < ${cutoff}
    `;
  } catch {
    // Non-critical — ignore cleanup errors.
  }
}

export function isAdminAuthConfigured(): boolean {
  return getPassword().length > 0 && getSecret().length > 0;
}

export function isPasswordValid(password: string): boolean {
  if (!isAdminAuthConfigured()) {
    return false;
  }

  const provided = Buffer.from(password);
  const expected = Buffer.from(getPassword());

  if (provided.length !== expected.length) {
    return false;
  }

  return timingSafeEqual(provided, expected);
}

export async function setAdminSessionCookie(): Promise<void> {
  const store = await cookies();
  store.set(ADMIN_SESSION_COOKIE, buildToken(), {
    httpOnly: true,
    sameSite: "lax",
    secure: process.env.NODE_ENV === "production",
    path: "/",
    domain: getAdminCookieDomain(),
    maxAge: 60 * 60 * 8,
  });
}

export async function clearAdminSessionCookie(): Promise<void> {
  const store = await cookies();
  store.set(ADMIN_SESSION_COOKIE, "", {
    httpOnly: true,
    sameSite: "lax",
    secure: process.env.NODE_ENV === "production",
    path: "/",
    domain: getAdminCookieDomain(),
    maxAge: 0,
  });
}

type FailState = {
  attempts: number;
  lockUntil: number;
};

function parseFailState(rawValue: string | undefined): FailState {
  if (!rawValue) {
    return { attempts: 0, lockUntil: 0 };
  }

  try {
    const parsed = JSON.parse(rawValue) as Partial<FailState>;

    return {
      attempts: Number(parsed.attempts ?? 0),
      lockUntil: Number(parsed.lockUntil ?? 0),
    };
  } catch {
    return { attempts: 0, lockUntil: 0 };
  }
}

export async function getAdminLockState(): Promise<{ locked: boolean; remainingSeconds: number }> {
  try {
    const ip = await getClientIp();
    const key = getLockoutKey(ip);
    const config = await prisma.appConfig.findUnique({
      where: { key },
      select: { value: true },
    });
    const state = parseFailState(config?.value);
    const now = Date.now();

    if (state.lockUntil > now) {
      return {
        locked: true,
        remainingSeconds: Math.ceil((state.lockUntil - now) / 1000),
      };
    }
  } catch {
    // DB unavailable — fail open to avoid blocking the login page.
  }

  return { locked: false, remainingSeconds: 0 };
}

export async function registerFailedAdminAttempt(): Promise<{ locked: boolean; remainingSeconds: number }> {
  try {
    const ip = await getClientIp();
    const key = getLockoutKey(ip);
    const now = Date.now();

    await cleanupExpiredLockouts();

    const config = await prisma.appConfig.findUnique({
      where: { key },
      select: { value: true },
    });

    const current = parseFailState(config?.value);
    // If a previous lockout has expired, reset the counter.
    const baseAttempts = current.lockUntil > 0 && current.lockUntil < now ? 0 : current.attempts;
    const attempts = baseAttempts + 1;
    const locked = attempts >= MAX_FAILED_ATTEMPTS;
    const lockUntil = locked ? now + LOCKOUT_SECONDS * 1000 : 0;

    await prisma.appConfig.upsert({
      where: { key },
      update: { value: JSON.stringify({ attempts, lockUntil }) },
      create: { key, value: JSON.stringify({ attempts, lockUntil }) },
    });

    return {
      locked,
      remainingSeconds: locked ? LOCKOUT_SECONDS : 0,
    };
  } catch {
    // DB error — don't lock out so admin can still log in.
    return { locked: false, remainingSeconds: 0 };
  }
}

export async function resetAdminFailedAttempts(): Promise<void> {
  try {
    const ip = await getClientIp();
    const key = getLockoutKey(ip);
    await prisma.appConfig.deleteMany({ where: { key } });
  } catch {
    // Non-critical — ignore.
  }
}

export async function isAdminAuthenticated(): Promise<boolean> {
  if (!isAdminAuthConfigured()) {
    return false;
  }

  const store = await cookies();
  const token = store.get(ADMIN_SESSION_COOKIE)?.value;

  if (!token) {
    return false;
  }

  return verifyToken(token);
}

/**
 * This project has no user accounts or roles table — there is a single
 * privileged session: the authenticated admin cookie checked above. There is
 * no separate "regular user" tier, so an authenticated admin session is by
 * definition the only "Super Admin". This is a readable alias only, kept as
 * a thin wrapper around `isAdminAuthenticated()` (not a new auth mechanism).
 *
 * Security note: this function (and any UI it gates, like the header's admin
 * shortcut button) is NOT the access-control boundary. Every admin page and
 * server action must independently guard itself with `requireAdminAuth()` or
 * an equivalent inline `isAdminAuthenticated()` check — never rely on a link
 * being hidden as the thing that keeps the admin area protected.
 */
export async function isSuperAdmin(): Promise<boolean> {
  return isAdminAuthenticated();
}

/**
 * Call at the top of every authenticated admin page or server action.
 * Clears the session cookie and redirects to the login page if not authenticated.
 */
export async function requireAdminAuth(): Promise<void> {
  if (!(await isAdminAuthenticated())) {
    await clearAdminSessionCookie();
    redirect(getAdminAppPath("/"));
  }
}
