import {
  Injectable,
  CanActivate,
  ExecutionContext,
  UnauthorizedException,
} from '@nestjs/common';
import { RedisService } from 'src/modules/backend/redis/redis.service';

/**
 * AccessGuard
 * -----------
 * This guard protects API routes by:
 * 1. Extracting the session ID from the request (cookie or Authorization header)
 * 2. Validating the session against Redis
 * 3. Checking role-based permissions for the requested controller & method
 * 4. Refreshing session TTL on every valid request
 */
@Injectable()
export class AccessGuard implements CanActivate {
  constructor(private readonly redisService: RedisService) {}

  /**
   * canActivate
   * -----------
   * Main guard entry point executed before the route handler.
   *
   * @param context ExecutionContext provided by NestJS
   * @returns boolean indicating whether the request is allowed
   * @throws UnauthorizedException when authentication or authorization fails
   */
  async canActivate(context: ExecutionContext): Promise<boolean> {
    // Extract HTTP request object
    const request = context.switchToHttp().getRequest();

    // Retrieve session ID from cookie or Authorization header
    const sessionId = this.extractSessionId(request);

    // Block request if no session identifier is found
    if (!sessionId) {
      throw new UnauthorizedException('Session ID not found');
    }

    // Fetch session data from Redis using the session ID
    const sessionData = await this.redisService.getSession(sessionId);

    // Reject request if session is invalid or expired
    if (!sessionData) {
      throw new UnauthorizedException('Invalid or expired session');
    }

    /**
     * Extract controller and method from request path
     * Example:
     *   /lead/create  -> controller = 'lead', method = 'create'
     */
    const segments = request.path.split('/').filter(Boolean);
    const controller = segments[0] || null;
    const method = segments[1] || null;

    /**
     * Parse role-based permissions from session
     * Expected structure:
     * {
     *   lead: ['create', 'update'],
     *   client: ['view']
     * }
     */
    const permissions = JSON.parse(sessionData.user.role_permissions);

    /**
     * Authorization check:
     * - Permissions must exist
     * - Controller must be defined
     * - Method must be allowed for that controller
     */
    if (
      !permissions ||
      !permissions[controller] ||
      !permissions[controller].includes(method)
    ) {
      throw new UnauthorizedException('Access denied');
    }

    /**
     * Refresh Redis session TTL
     * This ensures active users don't get logged out while making requests
     */
    await this.redisService.refreshSession(sessionId);

    /**
     * Attach session data to request object
     * Allows controllers, interceptors, or services to access user/session info
     */
    request.session = sessionData;

    // Allow request to proceed
    return true;
  }

  /**
   * extractSessionId
   * ----------------
   * Attempts to extract session ID from:
   * 1. HTTP cookies (session_id)
   * 2. Authorization header (Bearer token)
   *
   * @param request Incoming HTTP request
   * @returns session ID string or null if not found
   */
  private extractSessionId(request: any): string | null {
    // Attempt to extract session ID from cookies
    const cookies = request.headers.cookie;

    if (cookies) {
      const sessionCookie = cookies
        .split(';')
        .find((cookie) => cookie.trim().startsWith('session_id='));

      if (sessionCookie) {
        return sessionCookie.split('=')[1];
      }
    }

    // Attempt to extract session ID from Authorization header
    const authHeader = request.headers.authorization;
    if (authHeader && authHeader.startsWith('Bearer ')) {
      return authHeader.substring(7);
    }

    // No session identifier found
    return null;
  }
}
