/**
 * RBAC (Role-Based Access Control) Middleware
 * Enforces permissions based on user roles
 */

import { FastifyRequest, FastifyReply } from 'fastify';
import { getPrismaClient } from '../db/prisma.js';
import { createLogger } from '@4runr/shared';

const logger = createLogger('Gateway:RBAC');

export interface RBACContext {
  userId: string;
  roles: string[];
  permissions: string[];
}

/**
 * Check if user has permission for a resource and action
 */
export async function checkPermission(
  userId: string,
  resource: string,
  action: string
): Promise<boolean> {
  try {
    const prisma = await getPrismaClient();
    
    // Get user with roles and permissions
    const user = await prisma.user.findUnique({
      where: { id: userId },
      include: {
        roles: {
          include: {
            role: {
              include: {
                permissions: {
                  include: {
                    permission: true
                  }
                }
              }
            }
          }
        }
      }
    });

    if (!user || user.status !== 'ACTIVE') {
      return false;
    }

    // Check all permissions from all roles
    for (const userRole of user.roles) {
      for (const rolePermission of userRole.role.permissions) {
        const perm = rolePermission.permission;
        if (perm.resource === resource && perm.action === action) {
          return true;
        }
        // Wildcard support: "*" for resource or action
        if (perm.resource === '*' || perm.action === '*') {
          return true;
        }
        if (perm.resource === resource && perm.action === '*') {
          return true;
        }
        if (perm.resource === '*' && perm.action === action) {
          return true;
        }
      }
    }

    return false;
  } catch (error) {
    logger.error('Error checking permission', {
      userId,
      resource,
      action,
      error: error instanceof Error ? error.message : String(error)
    });
    return false;
  }
}

/**
 * Get user's RBAC context (roles and permissions)
 */
export async function getRBACContext(userId: string): Promise<RBACContext | null> {
  try {
    const prisma = await getPrismaClient();
    
    const user = await prisma.user.findUnique({
      where: { id: userId },
      include: {
        roles: {
          include: {
            role: {
              include: {
                permissions: {
                  include: {
                    permission: true
                  }
                }
              }
            }
          }
        }
      }
    });

    if (!user || user.status !== 'ACTIVE') {
      return null;
    }

    const roles: string[] = [];
    const permissions: Set<string> = new Set();

    for (const userRole of user.roles) {
      roles.push(userRole.role.name);
      for (const rolePermission of userRole.role.permissions) {
        const perm = rolePermission.permission;
        permissions.add(`${perm.resource}:${perm.action}`);
      }
    }

    return {
      userId: user.id,
      roles,
      permissions: Array.from(permissions)
    };
  } catch (error) {
    logger.error('Error getting RBAC context', {
      userId,
      error: error instanceof Error ? error.message : String(error)
    });
    return null;
  }
}

/**
 * Middleware factory to require a specific permission
 */
export function requirePermission(resource: string, action: string) {
  return async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {
    // Get user ID from auth context
    const auth = (request as any).auth || (request as any).user;
    if (!auth || !auth.sub) {
      reply.status(401).send({
        error: 'unauthorized',
        message: 'Authentication required'
      });
      return;
    }

    const userId = auth.sub;
    const hasPermission = await checkPermission(userId, resource, action);

    if (!hasPermission) {
      logger.warn('Permission denied', {
        userId,
        resource,
        action,
        path: request.url
      });

      reply.status(403).send({
        error: 'forbidden',
        message: `Permission denied: ${resource}:${action}`,
        required: `${resource}:${action}`
      });
      return;
    }

    // Attach RBAC context to request
    const rbacContext = await getRBACContext(userId);
    if (rbacContext) {
      (request as any).rbac = rbacContext;
    }
  };
}

/**
 * Middleware factory to require any of the specified permissions
 */
export function requireAnyPermission(permissions: Array<{ resource: string; action: string }>) {
  return async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {
    const auth = (request as any).auth || (request as any).user;
    if (!auth || !auth.sub) {
      reply.status(401).send({
        error: 'unauthorized',
        message: 'Authentication required'
      });
      return;
    }

    const userId = auth.sub;
    
    // Check if user has any of the required permissions
    for (const perm of permissions) {
      const hasPermission = await checkPermission(userId, perm.resource, perm.action);
      if (hasPermission) {
        // User has at least one required permission
        const rbacContext = await getRBACContext(userId);
        if (rbacContext) {
          (request as any).rbac = rbacContext;
        }
        return;
      }
    }

    // User doesn't have any of the required permissions
    logger.warn('Permission denied (requireAny)', {
      userId,
      required: permissions.map(p => `${p.resource}:${p.action}`),
      path: request.url
    });

    reply.status(403).send({
      error: 'forbidden',
      message: 'Permission denied',
      required: permissions.map(p => `${p.resource}:${p.action}`)
    });
  };
}

/**
 * Middleware factory to require all of the specified permissions
 */
export function requireAllPermissions(permissions: Array<{ resource: string; action: string }>) {
  return async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {
    const auth = (request as any).auth || (request as any).user;
    if (!auth || !auth.sub) {
      reply.status(401).send({
        error: 'unauthorized',
        message: 'Authentication required'
      });
      return;
    }

    const userId = auth.sub;
    
    // Check if user has all required permissions
    for (const perm of permissions) {
      const hasPermission = await checkPermission(userId, perm.resource, perm.action);
      if (!hasPermission) {
        logger.warn('Permission denied (requireAll)', {
          userId,
          missing: `${perm.resource}:${perm.action}`,
          path: request.url
        });

        reply.status(403).send({
          error: 'forbidden',
          message: `Permission denied: ${perm.resource}:${perm.action}`,
          required: permissions.map(p => `${p.resource}:${p.action}`)
        });
        return;
      }
    }

    // User has all required permissions
    const rbacContext = await getRBACContext(userId);
    if (rbacContext) {
      (request as any).rbac = rbacContext;
    }
  };
}

/**
 * Helper to check if user has a specific role
 */
export async function hasRole(userId: string, roleName: string): Promise<boolean> {
  try {
    const prisma = await getPrismaClient();
    
    const user = await prisma.user.findUnique({
      where: { id: userId },
      include: {
        roles: {
          include: {
            role: true
          }
        }
      }
    });

    if (!user || user.status !== 'ACTIVE') {
      return false;
    }

    return user.roles.some(ur => ur.role.name === roleName);
  } catch (error) {
    logger.error('Error checking role', {
      userId,
      roleName,
      error: error instanceof Error ? error.message : String(error)
    });
    return false;
  }
}

