import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import { Request } from 'express';

/**
 * AuthGuardOptional - Optional authentication guard for mixed-access routes
 *
 * This guard provides flexible authentication that:
 * - Validates JWT tokens if present (sets request.user)
 * - Allows access even if token is missing or invalid
 * - Supports both Authorization header and cookie-based tokens
 *
 * Use cases:
 * - Public endpoints with optional user context
 * - API endpoints that work for both authenticated and guest users
 * - Content that changes based on authentication status
 * - Graceful degradation when tokens expire
 *
 * @example
 * // Apply to controller or route
 * @UseGuards(AuthGuardOptional)
 * @Get('profile')
 * getProfile(@Request() req) {
 *   if (req.user) {
 *     return this.getUserProfile(req.user);
 *   }
 *   return this.getGuestProfile();
 * }
 */
@Injectable()
export class AuthGuardOptional implements CanActivate {
  constructor(
    private jwtService: JwtService,
    private configService: ConfigService,
  ) {}

  /**
   * Determines if the request should be allowed to proceed
   *
   * Always returns true (allowing access), but optionally validates
   * and attaches user payload to the request if a valid token is present.
   *
   * Flow:
   * 1. Extract token from request (header or cookie)
   * 2. If token exists, attempt to validate it
   * 3. If valid, attach payload to request.user
   * 4. If invalid, silently ignore and proceed
   * 5. Always return true to allow access
   *
   * @param context - Execution context containing request/response
   * @returns Always true (access always granted)
   */
  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    const token = this.extractTokenFromHeader(request);

    // Only process if token exists
    if (token) {
      try {
        // Verify JWT token
        const payload = await this.jwtService.verifyAsync(token, {
          secret: this.configService.get('JWT_SECRET'),
        });

        // 💡 Attach validated payload to request object
        // Route handlers can check `req.user` to determine authentication status
        request.user = payload;
      } catch (e) {
        // Token is invalid/expired - silently ignore and proceed without user context
        // Log for debugging in development
        // console.debug('Optional auth failed:', e.message);
        return true;
      }
    }

    // Always allow access (optional authentication succeeded or no token provided)
    return true;
  }

  /**
   * Extracts JWT token from request headers or cookies
   *
   * Supports multiple token locations in order of priority:
   * 1. Authorization header: "Bearer <token>"
   * 2. Cookie: "access_token=<token>"
   *
   * @param request - Express request object
   * @returns Token string if found, undefined otherwise
   *
   * @note Consider adding support for query parameter tokens (for debugging only)
   * @note Cookie parsing could be enhanced for multiple cookies and encoding
   */
  private extractTokenFromHeader(request: Request): string | undefined {
    // Method 1: Extract from Authorization header (Bearer token)
    const [_, token] = request.headers.authorization?.split(' ') ?? [];

    // Method 2: Extract from cookie
    const cookieString = request.headers.cookie;
    const match = cookieString?.match(/access_token=([^;]+)/);
    const accessToken = match && match.length >= 2 && match[1];

    // Return token from header (priority) or cookie
    return token ?? accessToken;
  }
}
