﻿import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";

import { prisma } from "@/lib/prisma";

type JwtPayload = {
  sub: string;
  role: string;
};

type AuthPrincipal = {
  username: string;
  role: string;
};

function getRequiredEnv(name: string) {
  const value = process.env[name];

  if (!value) {
    throw new Error(`Missing required environment variable: ${name}`);
  }

  return value;
}

function getJwtSecret() {
  return getRequiredEnv("SECRET_KEY");
}

function getJwtAlgorithm() {
  return process.env.JWT_ALGORITHM ?? "HS256";
}

function getJwtExpirationHours() {
  const rawValue = process.env.JWT_EXPIRATION_HOURS ?? "24";
  const parsedValue = Number(rawValue);

  return Number.isFinite(parsedValue) && parsedValue > 0 ? parsedValue : 24;
}

export function getTokenExpirationSeconds() {
  return getJwtExpirationHours() * 60 * 60;
}

function isBcryptHash(value: string) {
  return value.startsWith("$2a$") || value.startsWith("$2b$") || value.startsWith("$2y$");
}

async function resolvePasswordMatch(password: string, storedPassword: string) {
  if (isBcryptHash(storedPassword)) {
    return bcrypt.compare(password, storedPassword);
  }

  return password === storedPassword;
}

async function authenticateFromEnvironment(
  username: string,
  password: string,
): Promise<AuthPrincipal | null> {
  const expectedUsername = process.env.AUTH_ADMIN_USERNAME;

  if (!expectedUsername || username !== expectedUsername) {
    return null;
  }

  const passwordHash = process.env.AUTH_ADMIN_PASSWORD_HASH;
  const passwordPlain = process.env.AUTH_ADMIN_PASSWORD;

  if (passwordHash) {
    const passwordMatches = await bcrypt.compare(password, passwordHash);

    if (!passwordMatches) {
      return null;
    }

    return { username, role: "admin" };
  }

  if (passwordPlain) {
    if (password !== passwordPlain) {
      return null;
    }

    return { username, role: "admin" };
  }

  return null;
}

export async function authenticateAuthUser(
  username: string,
  password: string,
): Promise<AuthPrincipal | null> {
 try {
  const authUser = await prisma.auth_users.findUnique({
    where: { username },
  });

  if (!authUser) {
    return null;
  }

  if (!authUser.is_active) {
    return null;
  }

  if (!authUser.password_hash) {
    // Compte SSO : impossible de se connecter avec un mot de passe.
    return null;
  }

  const passwordMatches = await resolvePasswordMatch(
    password,
    authUser.password_hash,
  );

  if (passwordMatches) {
    return {
      username: authUser.username,
      role: authUser.role,
    };
  }
  } catch {
    // Database unavailable or auth_users not deployed yet: fall back to env-based auth.
  }

  const envAuthUser = await authenticateFromEnvironment(username, password);

  if (envAuthUser) {
    return envAuthUser;
  }

  // Preserve the previous hard failure when the environment is supposed to be
  // the source of truth but no usable credentials are configured.
  if (process.env.AUTH_ADMIN_USERNAME && !process.env.AUTH_ADMIN_PASSWORD_HASH && !process.env.AUTH_ADMIN_PASSWORD) {
    throw new Error(
      "Missing AUTH_ADMIN_PASSWORD_HASH or AUTH_ADMIN_PASSWORD environment variable",
    );
  }

  return null;
}

export async function verifyAdminCredentials(username: string, password: string) {
  return (await authenticateAuthUser(username, password)) !== null;
}

export function issueJwtToken(username: string, role: string = "admin") {
  const payload: JwtPayload = {
    sub: username,
    role,
  };

  return jwt.sign(payload, getJwtSecret(), {
    algorithm: getJwtAlgorithm() as jwt.Algorithm,
    expiresIn: `${getJwtExpirationHours()}h`,
  });
}

export function verifyJwtToken(token: string) {
  return jwt.verify(token, getJwtSecret(), {
    algorithms: [getJwtAlgorithm() as jwt.Algorithm],
  }) as jwt.JwtPayload & JwtPayload;
}

export function extractBearerToken(authorizationHeader: string | null) {
  if (!authorizationHeader) {
    return null;
  }

  const [scheme, token] = authorizationHeader.split(" ");

  if (scheme !== "Bearer" || !token) {
    return null;
  }

  return token;
}

export type SsoProfile = {
  email: string;
  name?: string | null;
  provider?: string;
  providerId?: string | null;
};

export async function authenticateSsoUser(profile: SsoProfile): Promise<AuthPrincipal | null> {
  const email = profile.email.trim().toLowerCase();

  if (!email.endsWith("@groupevitaminet.com")) {
    return null;
  }

  const existingUser = await prisma.auth_users.findFirst({
    where: {
      OR: [
        { email },
        { username: email },
      ],
    },
  });

  if (existingUser) {
    if (!existingUser.is_active) {
      return null;
    }

    const updatedUser = await prisma.auth_users.update({
      where: { id: existingUser.id },
      data: {
        email,
        provider: profile.provider ?? existingUser.provider ?? "microsoft",
        provider_id: profile.providerId ?? existingUser.provider_id,
        last_login_at: new Date(),
      },
    });

    return {
      username: updatedUser.username,
      role: updatedUser.role,
    };
  }

  const createdUser = await prisma.auth_users.create({
    data: {
      username: email,
      email,
      password_hash: null,
      role: "lecture",
      is_active: true,
      provider: profile.provider ?? "microsoft",
      provider_id: profile.providerId ?? null,
      last_login_at: new Date(),
    },
  });

  return {
    username: createdUser.username,
    role: createdUser.role,
  };
}