/**
 * RBAC Middleware - Role-Based Access Control
 * Propagated from erp-core (TASK-2026-01-30-RBAC)
 *
 * Provides permission-based authorization with hybrid role fallback
 * for gradual migration from role-based to permission-based access control.
 */

import { Request, Response, NextFunction } from 'express';
import { UnauthorizedError, ForbiddenError } from '../types/index';
import { logger } from '../utils/logger';

/**
 * Parse permission code format "resource:action" or "resource.action"
 */
function parsePermissionCode(code: string): { resource: string; action: string } {
  const separator = code.includes(':') ? ':' : '.';
  const parts = code.split(separator);
  if (parts.length < 2 || !parts[0] || !parts[1]) {
    throw new Error(`Invalid permission code format: ${code}. Expected "resource:action" or "resource.action"`);
  }
  return { resource: parts[0], action: parts[1] };
}

/**
 * Check if user has permission (role-based fallback for verticals without full RBAC)
 */
async function userHasPermission(
  roles: string[],
  resource: string,
  action: string
): Promise<boolean> {
  // Super admin bypass
  if (roles.includes('super_admin')) {
    return true;
  }

  // Admin has all permissions except system:manage
  if (roles.includes('admin') && !(resource === 'system' && action === 'manage')) {
    return true;
  }

  // Manager has CRUD on most resources
  if (roles.includes('manager')) {
    const managerResources = [
      'projects', 'estimates', 'budgets', 'inventory', 'purchases',
      'sales', 'partners', 'employees', 'timesheets', 'reports'
    ];
    if (managerResources.includes(resource)) {
      return true;
    }
  }

  // TODO: Implement database permission check when full RBAC is ready
  // return permissionsService.hasPermission(tenantId, userId, resource, action);

  return false;
}

/**
 * Shorthand middleware for permission check using code format
 * @param code Permission in format "resource:action" (e.g., "invoices:create")
 */
export function requirePerm(code: string) {
  const { resource, action } = parsePermissionCode(code);

  return async (req: Request, _res: Response, next: NextFunction): Promise<void> => {
    try {
      if (!req.user) {
        throw new UnauthorizedError('Usuario no autenticado');
      }

      const roles = req.user.roles || [];

      const hasPermission = await userHasPermission(roles, resource, action);

      if (hasPermission) {
        return next();
      }

      logger.warn('Access denied (requirePerm)', {
        userId: req.user.sub,
        permission: code,
        roles,
      });
      throw new ForbiddenError(`No tiene permiso: ${code}`);
    } catch (error) {
      next(error);
    }
  };
}

/**
 * Require ANY of the specified permissions (OR logic)
 * @param codes Permission codes (e.g., "invoices:read", "invoices:list")
 */
export function requireAnyPerm(...codes: string[]) {
  const permissions = codes.map(parsePermissionCode);

  return async (req: Request, _res: Response, next: NextFunction): Promise<void> => {
    try {
      if (!req.user) {
        throw new UnauthorizedError('Usuario no autenticado');
      }

      const roles = req.user.roles || [];

      // Super admin bypass
      if (roles.includes('super_admin')) {
        return next();
      }

      // Check each permission
      for (const { resource, action } of permissions) {
        const hasPermission = await userHasPermission(roles, resource, action);
        if (hasPermission) {
          return next();
        }
      }

      logger.warn('Access denied (requireAnyPerm)', {
        userId: req.user.sub,
        permissions: codes,
        roles,
      });
      throw new ForbiddenError(`No tiene ninguno de los permisos requeridos: ${codes.join(', ')}`);
    } catch (error) {
      next(error);
    }
  };
}

/**
 * Require ALL of the specified permissions (AND logic)
 * @param codes Permission codes (e.g., "invoices:read", "payments:create")
 */
export function requireAllPerms(...codes: string[]) {
  const permissions = codes.map(parsePermissionCode);

  return async (req: Request, _res: Response, next: NextFunction): Promise<void> => {
    try {
      if (!req.user) {
        throw new UnauthorizedError('Usuario no autenticado');
      }

      const roles = req.user.roles || [];

      // Super admin bypass
      if (roles.includes('super_admin')) {
        return next();
      }

      // Check all permissions
      const missingPermissions: string[] = [];
      for (let i = 0; i < permissions.length; i++) {
        const { resource, action } = permissions[i];
        const hasPermission = await userHasPermission(roles, resource, action);
        if (!hasPermission) {
          missingPermissions.push(codes[i]);
        }
      }

      if (missingPermissions.length === 0) {
        return next();
      }

      logger.warn('Access denied (requireAllPerms)', {
        userId: req.user.sub,
        required: codes,
        missing: missingPermissions,
        roles,
      });
      throw new ForbiddenError(`Faltan permisos: ${missingPermissions.join(', ')}`);
    } catch (error) {
      next(error);
    }
  };
}

/**
 * Hybrid access control for gradual migration from role-based to permission-based
 * Allows access if user has ANY of the specified roles OR has the specified permission
 * @param options.roles Legacy roles to check (fallback)
 * @param options.permission Permission code to check (preferred)
 */
export function requireAccess(options: { roles?: string[]; permission?: string }) {
  const { roles: requiredRoles, permission } = options;
  const parsedPermission = permission ? parsePermissionCode(permission) : null;

  return async (req: Request, _res: Response, next: NextFunction): Promise<void> => {
    try {
      if (!req.user) {
        throw new UnauthorizedError('Usuario no autenticado');
      }

      const roles = req.user.roles || [];

      // Super admin bypass
      if (roles.includes('super_admin')) {
        return next();
      }

      // Check permission first (if specified)
      if (parsedPermission) {
        const hasPermission = await userHasPermission(
          roles,
          parsedPermission.resource,
          parsedPermission.action
        );
        if (hasPermission) {
          return next();
        }
      }

      // Fallback to role check (if specified)
      if (requiredRoles && requiredRoles.length > 0) {
        const hasRole = requiredRoles.some(role => roles.includes(role));
        if (hasRole) {
          logger.debug('Access granted via role (hybrid mode)', {
            userId: req.user.sub,
            permission,
            matchedRoles: requiredRoles.filter(r => roles.includes(r)),
          });
          return next();
        }
      }

      logger.warn('Access denied (requireAccess)', {
        userId: req.user.sub,
        permission,
        requiredRoles,
        userRoles: roles,
      });

      const errorDetails = [];
      if (permission) errorDetails.push(`permiso: ${permission}`);
      if (requiredRoles) errorDetails.push(`roles: ${requiredRoles.join(', ')}`);
      throw new ForbiddenError(`Acceso denegado. Requiere ${errorDetails.join(' o ')}`);
    } catch (error) {
      next(error);
    }
  };
}

/**
 * Check permission for resource owner (user can access their own resources)
 * @param code Permission code for non-owners
 * @param ownerField Field name in request params that contains owner ID (default: 'userId')
 */
export function requirePermOrOwner(code: string, ownerField: string = 'userId') {
  const { resource, action } = parsePermissionCode(code);

  return async (req: Request, _res: Response, next: NextFunction): Promise<void> => {
    try {
      if (!req.user) {
        throw new UnauthorizedError('Usuario no autenticado');
      }

      const userId = req.user.sub || (req.user as any).userId;
      const roles = req.user.roles || [];
      const resourceOwnerId = req.params[ownerField] || req.body?.[ownerField];

      // Super admin bypass
      if (roles.includes('super_admin')) {
        return next();
      }

      // Check if user is the owner
      if (resourceOwnerId === userId) {
        logger.debug('Access granted (owner)', { userId, resource, action });
        return next();
      }

      // Check permission
      const hasPermission = await userHasPermission(roles, resource, action);
      if (hasPermission) {
        return next();
      }

      logger.warn('Access denied (requirePermOrOwner)', {
        userId,
        permission: code,
        resourceOwnerId,
        roles,
      });
      throw new ForbiddenError(`No tiene permiso: ${code}`);
    } catch (error) {
      next(error);
    }
  };
}
