import crypto from "node:crypto";

import { NextFunction, Request, Response } from "express";
import jwt from "jsonwebtoken";

import { config } from "./config.js";
import { createLogger } from "./logger.js";
import { getRuntimeSettings } from "./runtimeSettings.js";

const logger = createLogger(config.logLevel);

const TOKEN_AUDIENCE = "paperless-contract-companion";
const TOKEN_ISSUER = "paperless-contract-companion-service";

export interface AuthenticatedRequest extends Request {
  user?: {
    username: string;
  };
}

interface TokenPayload extends jwt.JwtPayload {
  sub: string;
  role: "admin";
}

function safeCompare(expected: string | undefined, provided: string | undefined): boolean {
  if (typeof expected !== "string" || typeof provided !== "string") {
    return false;
  }

  const expectedBuffer = Buffer.from(expected);
  const providedBuffer = Buffer.from(provided);

  if (expectedBuffer.length !== providedBuffer.length) {
    return false;
  }

  return crypto.timingSafeEqual(expectedBuffer, providedBuffer);
}

function getCurrentCredentials(): { username: string | null; password: string | null } {
  const runtime = getRuntimeSettings();
  const username = runtime.authUsername ?? config.authUsername ?? null;
  const password = runtime.authPassword ?? config.authPassword ?? null;
  return { username, password };
}

export function isAuthEnabled(): boolean {
  const { username, password } = getCurrentCredentials();
  return Boolean(username && password && config.authJwtSecret);
}

function ensureAuthConfigured() {
  if (!config.authJwtSecret) {
    throw new Error("AUTH_JWT_SECRET must be set when authentication is enabled.");
  }
}

export function verifyCredentials(username: string, password: string): boolean {
  if (!isAuthEnabled()) {
    logger.warn("Authentication disabled; all credentials are accepted.");
    return true;
  }

  const { username: expectedUsername, password: expectedPassword } = getCurrentCredentials();

  return safeCompare(expectedUsername ?? undefined, username) && safeCompare(expectedPassword ?? undefined, password);
}

export function createAccessToken(username: string): { token: string; expiresAt: string } {
  ensureAuthConfigured();

  const expiresInSeconds = config.authTokenExpiresInHours * 60 * 60;

  const payload: TokenPayload = {
    sub: username,
    role: "admin",
    iss: TOKEN_ISSUER,
    aud: TOKEN_AUDIENCE
  };

  const token = jwt.sign(payload, config.authJwtSecret!, {
    expiresIn: expiresInSeconds,
    algorithm: "HS256"
  });

  const expiresAt = new Date(Date.now() + expiresInSeconds * 1000).toISOString();
  return { token, expiresAt };
}

export function authenticateRequest(
  req: AuthenticatedRequest,
  res: Response,
  next: NextFunction
) {
  if (!isAuthEnabled()) {
    return next();
  }

  const authorization = req.get("authorization");

  if (!authorization || !authorization.startsWith("Bearer ")) {
    return res.status(401).json({ error: "Missing or invalid authorization header" });
  }

  const token = authorization.slice("Bearer ".length);

  try {
    ensureAuthConfigured();
    const payload = jwt.verify(token, config.authJwtSecret!, {
      audience: TOKEN_AUDIENCE,
      issuer: TOKEN_ISSUER,
      algorithms: ["HS256"]
    }) as TokenPayload;

    req.user = { username: payload.sub };
    return next();
  } catch (error) {
    logger.warn("Failed to authenticate request: %s", (error as Error).message);
    return res.status(401).json({ error: "Invalid or expired token" });
  }
}
