import {
  Injectable,
  CanActivate,
  ExecutionContext,
  ForbiddenException,
  InternalServerErrorException,
  Logger,
} from "@nestjs/common";
import { Prisma } from "@prisma/client";
import { Reflector } from "@nestjs/core";
import { PrismaService } from "../../prisma/prisma.service";
import { PERMISSION_KEY, Permission } from "../decorators/permissions.decorator";
import { WorkspaceMemberRole } from "@prisma/client";
import type { RequestWithWorkspace } from "../types/user.types";

/**
 * PermissionGuard enforces role-based access control for workspace operations.
 *
 * This guard must be used after AuthGuard and WorkspaceGuard, as it depends on:
 * - request.user.id (set by AuthGuard)
 * - request.workspace.id (set by WorkspaceGuard)
 *
 * @example
 * ```typescript
 * @Controller('workspaces')
 * @UseGuards(AuthGuard, WorkspaceGuard, PermissionGuard)
 * export class WorkspacesController {
 *   @RequirePermission(Permission.WORKSPACE_ADMIN)
 *   @Delete(':id')
 *   async deleteWorkspace() {
 *     // Only ADMIN or OWNER can execute this
 *   }
 *
 *   @RequirePermission(Permission.WORKSPACE_MEMBER)
 *   @Get('tasks')
 *   async getTasks() {
 *     // Any workspace member can execute this
 *   }
 * }
 * ```
 */
@Injectable()
export class PermissionGuard implements CanActivate {
  private readonly logger = new Logger(PermissionGuard.name);

  constructor(
    private readonly reflector: Reflector,
    private readonly prisma: PrismaService
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    // Get required permission from decorator
    const requiredPermission = this.reflector.getAllAndOverride<Permission | undefined>(
      PERMISSION_KEY,
      [context.getHandler(), context.getClass()]
    );

    // If no permission is specified, allow access
    if (!requiredPermission) {
      return true;
    }

    const request = context.switchToHttp().getRequest<RequestWithWorkspace>();
    // Note: Despite types, user/workspace may be null if guards didn't run
    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
    const userId = request.user?.id;
    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
    const workspaceId = request.workspace?.id;

    if (!userId || !workspaceId) {
      this.logger.error(
        "PermissionGuard: Missing user or workspace context. Ensure AuthGuard and WorkspaceGuard are applied first."
      );
      throw new ForbiddenException("Authentication and workspace context required");
    }

    // Get user's role in the workspace
    const userRole = await this.getUserWorkspaceRole(userId, workspaceId);

    if (!userRole) {
      throw new ForbiddenException("You are not a member of this workspace");
    }

    // Check if user's role meets the required permission
    const hasPermission = this.checkPermission(userRole, requiredPermission);

    if (!hasPermission) {
      this.logger.warn(
        `Permission denied: User ${userId} with role ${userRole} attempted to access ${requiredPermission} in workspace ${workspaceId}`
      );
      throw new ForbiddenException(`Insufficient permissions. Required: ${requiredPermission}`);
    }

    // Attach role to request for convenience
    request.user.workspaceRole = userRole;

    this.logger.debug(`Permission granted: User ${userId} (${userRole}) → ${requiredPermission}`);

    return true;
  }

  /**
   * Fetches the user's role in a workspace
   *
   * SEC-API-3 FIX: Database errors are no longer swallowed as null role.
   * Connection timeouts, pool exhaustion, and other infrastructure errors
   * are propagated as 500 errors to avoid masking operational issues.
   */
  private async getUserWorkspaceRole(
    userId: string,
    workspaceId: string
  ): Promise<WorkspaceMemberRole | null> {
    try {
      const member = await this.prisma.workspaceMember.findUnique({
        where: {
          workspaceId_userId: {
            workspaceId,
            userId,
          },
        },
        select: {
          role: true,
        },
      });

      return member?.role ?? null;
    } catch (error) {
      // Only handle Prisma "not found" errors (P2025) as expected cases
      // All other database errors (connection, timeout, pool) should propagate
      if (
        error instanceof Prisma.PrismaClientKnownRequestError &&
        error.code === "P2025" // Record not found
      ) {
        return null;
      }

      // Log the error before propagating
      this.logger.error(
        `Database error during permission check: ${error instanceof Error ? error.message : "Unknown error"}`,
        error instanceof Error ? error.stack : undefined
      );

      // Propagate infrastructure errors as 500s, not permission denied
      throw new InternalServerErrorException("Failed to verify permissions");
    }
  }

  /**
   * Checks if a user's role satisfies the required permission level
   */
  private checkPermission(userRole: WorkspaceMemberRole, requiredPermission: Permission): boolean {
    switch (requiredPermission) {
      case Permission.WORKSPACE_OWNER:
        return userRole === WorkspaceMemberRole.OWNER;

      case Permission.WORKSPACE_ADMIN:
        return userRole === WorkspaceMemberRole.OWNER || userRole === WorkspaceMemberRole.ADMIN;

      case Permission.WORKSPACE_MEMBER:
        return (
          userRole === WorkspaceMemberRole.OWNER ||
          userRole === WorkspaceMemberRole.ADMIN ||
          userRole === WorkspaceMemberRole.MEMBER
        );

      case Permission.WORKSPACE_ANY:
        // Any role including GUEST
        return true;

      default: {
        const exhaustiveCheck: never = requiredPermission;
        this.logger.error(`Unknown permission: ${String(exhaustiveCheck)}`);
        return false;
      }
    }
  }
}
