/**
 * Helpers de autorización basados en **roles de plataforma** (staff interno de
 * Horux 360). Ortogonal al rol per-tenant (owner, cfo, contador, auxiliar, visor).
 *
 * Migración desde el modelo anterior:
 *   - Antes: `isGlobalAdmin(tenantId, role)` checaba si el user era owner del
 *     tenant con RFC HTS240708LJA. Una sola persona admin global.
 *   - Ahora: `isGlobalAdmin()` sigue existiendo como alias de `hasPlatformRole('platform_admin')`,
 *     pero múltiples users pueden tener ese rol.
 *   - Compatibilidad: si la tabla `user_platform_roles` está vacía (pre-migración),
 *     cae al check por RFC como fallback. Post-seed, los owners del tenant dueño
 *     ya están registrados como `platform_admin` y el check funciona sin fallback.
 *
 * Cache in-memory 5min por (userId, role) para evitar hit a BD en cada request.
 */
import { prisma } from '../config/database.js';
import { GLOBAL_ADMIN_RFC } from '@horux/shared';

export type PlatformRole = 'platform_admin' | 'platform_ti' | 'platform_support' | 'platform_sales' | 'platform_finance';

/** Roles superset — implican todos los demás. */
const SUPERSET_ROLES: PlatformRole[] = ['platform_admin', 'platform_ti'];

function hasSuperset(roles: Set<PlatformRole>): boolean {
  return SUPERSET_ROLES.some(r => roles.has(r));
}

interface CacheEntry {
  roles: Set<PlatformRole>;
  expires: number;
}

const cache = new Map<string, CacheEntry>();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutos

export function invalidatePlatformRolesCache(userId: string) {
  cache.delete(userId);
}

/**
 * Carga los roles de plataforma de un user. Hit a BD con cache de 5 min.
 * Si el user no tiene rows en `user_platform_roles`, devuelve Set vacío.
 */
async function loadPlatformRoles(userId: string): Promise<Set<PlatformRole>> {
  const cached = cache.get(userId);
  if (cached && cached.expires > Date.now()) return cached.roles;

  const rows = await prisma.userPlatformRole.findMany({
    where: { userId },
    select: { role: true },
  });
  const roles = new Set(rows.map(r => r.role as PlatformRole));
  cache.set(userId, { roles, expires: Date.now() + CACHE_TTL });
  return roles;
}

/**
 * ¿El user tiene el rol de plataforma indicado?
 * `platform_admin` automáticamente implica TODOS los otros roles (superrol).
 */
export async function hasPlatformRole(userId: string | undefined, role: PlatformRole): Promise<boolean> {
  if (!userId) return false;
  const roles = await loadPlatformRoles(userId);
  if (hasSuperset(roles)) return true;
  return roles.has(role);
}

/**
 * ¿El user tiene alguno de los roles indicados?
 */
export async function hasAnyPlatformRole(userId: string | undefined, ...allowedRoles: PlatformRole[]): Promise<boolean> {
  if (!userId) return false;
  const roles = await loadPlatformRoles(userId);
  if (hasSuperset(roles)) return true;
  return allowedRoles.some(r => roles.has(r));
}

/** Atajos granulares — usar en middlewares o guards de endpoints */
export const canManageTenants = (userId?: string) =>
  hasAnyPlatformRole(userId, 'platform_admin', 'platform_sales', 'platform_support');

export const canEditPrices = (userId?: string) =>
  hasAnyPlatformRole(userId, 'platform_admin', 'platform_finance');

export const canEmitInvoicesManual = (userId?: string) =>
  hasAnyPlatformRole(userId, 'platform_admin', 'platform_finance');

export const isPlatformStaff = async (userId?: string) => {
  if (!userId) return false;
  const roles = await loadPlatformRoles(userId);
  return roles.size > 0;
};

/**
 * Obtiene los roles actuales del user (para incluir en JWT al login).
 */
export async function getPlatformRoles(userId: string): Promise<PlatformRole[]> {
  const roles = await loadPlatformRoles(userId);
  return Array.from(roles);
}

/**
 * Compatibilidad con el código legacy que usa `isGlobalAdmin(tenantId, role)`.
 * Ahora resuelve vía tabla `user_platform_roles` → rol `platform_admin`.
 *
 * Fallback: si no hay rows en la tabla para el user que está intentando, pero es
 * owner del tenant HTS240708LJA, se considera platform_admin (cubre el escenario
 * post-deploy pre-seed).
 */
export async function isGlobalAdmin(tenantId: string, role: string, userId?: string): Promise<boolean> {
  // Si se pasa userId y tiene rol de plataforma superset, permitir acceso global
  if (userId && await hasAnyPlatformRole(userId, ...SUPERSET_ROLES)) {
    return true;
  }

  // Las firmas viejas no tienen userId disponible. Lo resolvemos buscando el user
  // que matchea tenantId + rol 'owner'. Para evitar ese hit extra, la preferencia
  // es usar `hasPlatformRole(req.user.userId, 'platform_admin')` en código nuevo.
  if (role !== 'owner') return false;

  // Primer intento: ¿hay users con rol superset (admin o TI) en este tenant?
  // Vía memberships: el user tiene un superset role Y es miembro del tenant.
  const adminRow = await prisma.userPlatformRole.findFirst({
    where: {
      role: { in: SUPERSET_ROLES },
      user: { memberships: { some: { tenantId, active: true } } },
    },
    select: { userId: true },
  });
  if (adminRow) return true;

  // Fallback: tenant con RFC admin global (compat antes del backfill)
  const tenant = await prisma.tenant.findUnique({
    where: { id: tenantId },
    select: { rfc: true },
  });
  return tenant?.rfc === GLOBAL_ADMIN_RFC;
}
