// src/server/shared/plugins/rbac.ts
// Role-Based Access Control (RBAC) plugin with permission registry

import fp from 'fastify-plugin';
import { FastifyRequest, FastifyReply } from 'fastify';
import { db } from '../db/client';
import { rolePermissionsTable, systemModulesTable } from '@features/system/db';
import { eq, and } from 'drizzle-orm';
import { UserRole, UserInfo } from '../lib/auth';
import { AuthorizationError } from '../lib/auth';
import { AppPermission } from '@shared/contracts/permissions';

// In-Memory cache for permissions
let permissionsCache: Record<string, string[]> = {};
let cacheTimestamps: Record<string, number> = {};
const CACHE_TTL = 60 * 1000; // 1 minute

/**
 * Clear RBAC permissions cache.
 * If role is provided, clears cache only for that role.
 * If role is omitted, clears entire permissions cache.
 */
export function clearPermissionsCache(role?: string): void {
  if (role) {
    delete permissionsCache[role];
    delete cacheTimestamps[role];
  } else {
    permissionsCache = {};
    cacheTimestamps = {};
  }
}

/**
 * Get cached permissions for a role
 * @param role - User role
 * @returns Array of permission strings
 */
async function getCachedPermissions(role: string): Promise<string[]> {
  const now = Date.now();
  if (now - (cacheTimestamps[role] || 0) > CACHE_TTL || !permissionsCache[role]) {
    const rows = await db.select().from(rolePermissionsTable)
      .where(and(eq(rolePermissionsTable.role, role), eq(rolePermissionsTable.isEnabled, true)));

    permissionsCache[role] = rows.map(r => `${r.resource}:${r.action}`);
    cacheTimestamps[role] = now;
  }
  return permissionsCache[role] || [];
}

/**
 * Check if a user has a specific permission from database (Dynamic PBAC)
 *
 * @param role - User role
 * @param resource - Resource to check
 * @param action - Action to check
 * @returns True if permission exists and is enabled
 */
async function checkDynamicPermission(role: string, resource: string, action: string): Promise<boolean> {
  try {
    const userPermissions = await getCachedPermissions(role);
    return userPermissions.includes(`${resource}:${action}`);
  } catch (error) {
    // If database check fails, return false for security
    console.warn('Failed to check dynamic permission:', error);
    return false;
  }
}

/**
 * Check if a module is enabled
 * 
 * @param moduleId - Module ID to check
 * @returns True if module is enabled
 */
async function checkModuleEnabled(moduleId: string): Promise<boolean> {
  try {
    const [module] = await db
      .select()
      .from(systemModulesTable)
      .where(eq(systemModulesTable.moduleId, moduleId))
      .limit(1);
      
    return module?.isEnabled ?? false;
  } catch (error) {
    // If database check fails, assume module is enabled (fail-open for safety)
    console.warn('Failed to check module enabled status, assuming enabled:', error);
    return true;
  }
}

/**
 * Parse permission string into resource and action
 * 
 * @param permission - Permission string (e.g., 'rooms:read')
 * @returns Object with resource and action
 */
function parsePermission(permission: string): { resource: string; action: string } | null {
  const parts = permission.split(':');
  if (parts.length !== 2) {
    return null;
  }
  return { resource: parts[0], action: parts[1] };
}

/**
 * Check if a user has a specific permission
 *
 * @param user - User info from JWT
 * @param permission - Permission to check
 * @returns True if user has the permission
 */
export async function hasPermission(
  user: UserInfo | undefined,
  permission: AppPermission
): Promise<boolean> {
  if (!user) {
    return false;
  }

  // Parse permission string
  const parsed = parsePermission(permission);
  if (!parsed) {
    return false;
  }

  // Check dynamic permission from database
  return await checkDynamicPermission(user.role, parsed.resource, parsed.action);
}

/**
 * Check if a user has any of the specified permissions
 * 
 * @param user - User info from JWT
 * @param permissions - Permissions to check
 * @returns True if user has any of the permissions
 */
export async function hasAnyPermission(
  user: UserInfo | undefined,
  permissions: AppPermission[]
): Promise<boolean> {
  if (!user) {
    return false;
  }

  for (const permission of permissions) {
    const hasPerm = await hasPermission(user, permission);
    if (hasPerm) {
      return true;
    }
  }

  return false;
}

/**
 * Check if a user has all of the specified permissions
 * 
 * @param user - User info from JWT
 * @param permissions - Permissions to check
 * @returns True if user has all of the permissions
 */
export async function hasAllPermissions(
  user: UserInfo | undefined,
  permissions: AppPermission[]
): Promise<boolean> {
  if (!user) {
    return false;
  }

  for (const permission of permissions) {
    const hasPerm = await hasPermission(user, permission);
    if (!hasPerm) {
      return false;
    }
  }

  return true;
}

/**
 * RBAC plugin for Fastify
 * 
 * Adds a `.can()` decorator to check permissions
 */
export default fp(async (fastify) => {
  /**
   * Decorator to check if current user has a specific permission
   * 
   * @param permission - Permission to check
   * @returns True if user has the permission
   * 
   * @example
   * ```ts
   * fastify.get('/api/rooms', { onRequest: [fastify.authenticate, fastify.can(AppPermission.ROOMS_READ)] }, async (request, reply) => {
   *   // Handler logic
   * });
   * ```
   */
  // @ts-expect-error - Changed return type from boolean to Promise<void> to ensure hook completes
  fastify.decorate('can', (permission: string, moduleId?: string) => {
    return async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {
      // Get user from request (set by JWT plugin)
      const user = request.user as UserInfo | undefined;

      if (!user) {
        throw new AuthorizationError('Authentication required', 401);
      }

      // Check module status if moduleId is provided
      if (moduleId) {
        const isModuleEnabled = await checkModuleEnabled(moduleId);
        if (!isModuleEnabled) {
          throw new AuthorizationError('Модуль отключен', 403);
        }
      }

      const allowed = await hasPermission(user, permission as AppPermission);
      if (!allowed) {
        throw new AuthorizationError(`Missing permission: ${permission}`, 403);
      }
      // Явный возврат из async функции разрешает запрос
      return;
    };
  });

  /**
   * Decorator to check if current user has any of the specified permissions
   * 
   * @param permissions - Permissions to check
   * @returns True if user has any of the permissions
   * 
   * @example
   * ```ts
   * fastify.get('/api/notes', { onRequest: [fastify.authenticate, fastify.canAny(AppPermission.NOTES_READ, AppPermission.NOTIFICATIONS_READ)] }, async (request, reply) => {
   *   // Handler logic
   * });
   * ```
   */
  // @ts-expect-error - Changed return type from boolean to Promise<void> to ensure hook completes
  fastify.decorate('canAny', (...permissions: AppPermission[]) => {
    return async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {
      const user = request.user as UserInfo | undefined;

      if (!user) {
        throw new AuthorizationError('Authentication required', 401);
      }

      const allowed = await hasAnyPermission(user, permissions);
      if (!allowed) {
        throw new AuthorizationError(`Missing one of permissions: ${permissions.join(', ')}`, 403);
      }
      // Явный возврат из async функции разрешает запрос
      return;
    };
  });

  /**
   * Decorator to check if current user has all of the specified permissions
   * 
   * @param permissions - Permissions to check
   * @returns True if user has all of the permissions
   * 
   * @example
   * ```ts
    * fastify.post('/api/rooms', { onRequest: [fastify.authenticate, fastify.canAll(AppPermission.ROOMS_WRITE, AppPermission.CHECKIN_WRITE)] }, async (request, reply) => {
   *   // Handler logic
   * });
   * ```
   */
  // @ts-expect-error - Changed return type from boolean to Promise<void> to ensure hook completes
  fastify.decorate('canAll', (...permissions: AppPermission[]) => {
    return async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {
      const user = request.user as UserInfo | undefined;

      if (!user) {
        throw new AuthorizationError('Authentication required', 401);
      }

      const allowed = await hasAllPermissions(user, permissions);
      if (!allowed) {
        throw new AuthorizationError(`Missing all required permissions: ${permissions.join(', ')}`, 403);
      }
      // Явный возврат из async функции разрешает запрос
      return;
    };
  });

  /**
   * Helper method to check permissions without throwing errors
   * 
   * @param permission - Permission to check
   * @returns True if user has the permission
   * 
   * @example
   * ```ts
   * const canDelete = fastify.checkPermission(AppPermission.ROOMS_DELETE);
   * if (canDelete) {
   *   // Show delete button
   * }
   * ```
   */
  fastify.decorate('checkPermission', (permission: AppPermission): boolean => {
    // This is intended for use in route handlers where request.user is available
    // For use outside of request context, pass user explicitly
    return true; // Placeholder - actual check requires user context
  });
}, { name: 'rbac' });
