import type { Request, Response, NextFunction } from 'express';
import { verifyToken } from '../auth/tokens.js';
import { AppError } from './error.middleware.js';
import { prisma } from '../config/database.js';
import type { JWTPayload, Role } from '@horux/shared';

declare global {
  namespace Express {
    interface Request {
      user?: JWTPayload;
    }
  }
}

/**
 * Cache de `tokenVersion` por userId con TTL 30s. Evita hit a BD en cada
 * request autenticada. Al incrementar `tokenVersion` (password change,
 * logout-all), se llama `invalidateTokenVersionCache(userId)` que borra la
 * entrada y broadcast entre workers PM2.
 */
const tokenVersionCache = new Map<string, { version: number; expires: number }>();
const TOKEN_VERSION_TTL_MS = 30 * 1000;

export function invalidateTokenVersionCache(userId: string) {
  tokenVersionCache.delete(userId);
  if (typeof process.send === 'function') {
    process.send({ type: 'invalidate-token-version', userId });
  }
}

// Escucha broadcasts entre workers PM2 cluster
if (typeof process.on === 'function') {
  process.on('message', (msg: any) => {
    if (msg && msg.type === 'invalidate-token-version' && typeof msg.userId === 'string') {
      tokenVersionCache.delete(msg.userId);
    }
  });
}

async function getCurrentTokenVersion(userId: string): Promise<number | null> {
  const cached = tokenVersionCache.get(userId);
  if (cached && cached.expires > Date.now()) return cached.version;

  const user = await prisma.user.findUnique({
    where: { id: userId },
    select: { tokenVersion: true, active: true },
  });
  if (!user || !user.active) return null; // User borrado o desactivado → rechaza

  tokenVersionCache.set(userId, {
    version: user.tokenVersion,
    expires: Date.now() + TOKEN_VERSION_TTL_MS,
  });
  return user.tokenVersion;
}

export async function authenticate(req: Request, res: Response, next: NextFunction) {
  const authHeader = req.headers.authorization;

  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return next(new AppError(401, 'Token no proporcionado'));
  }

  const token = authHeader.split(' ')[1];

  let payload: JWTPayload;
  try {
    payload = verifyToken(token);
  } catch (error) {
    return next(new AppError(401, 'Token inválido o expirado'));
  }

  // Check tokenVersion contra BD. JWT con versión menor a la actual del user
  // quedan rechazados (forzando re-login tras password change / logout-all).
  const currentVersion = await getCurrentTokenVersion(payload.userId);
  if (currentVersion === null) {
    return next(new AppError(401, 'Sesión inválida. Vuelve a iniciar sesión.'));
  }
  const jwtVersion = payload.tokenVersion ?? 0;
  if (jwtVersion !== currentVersion) {
    return next(new AppError(401, 'Sesión expirada. Por seguridad, inicia sesión de nuevo.'));
  }

  req.user = payload;
  next();
}

export function authorize(...roles: Role[]) {
  return (req: Request, res: Response, next: NextFunction) => {
    if (!req.user) {
      return next(new AppError(401, 'No autenticado'));
    }

    if (roles.length > 0 && !roles.includes(req.user.role)) {
      return next(new AppError(403, 'No autorizado'));
    }

    next();
  };
}
