import {
  CanActivate,
  ExecutionContext,
  Injectable,
  UnauthorizedException,
  Inject,
  Logger,
  ForbiddenException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Request } from 'express';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import { IAdminTokenPayload } from '../interfaces/IToken-payload';
import { PERMISSIONS_KEY } from '../../../common/decorators/permissions.decorator';
import { PermissionsService } from 'src/modules/roles/providers/permissions.service';

export interface AdminAuthRequest extends Request {
  adminId: string;
  restId: string;
}

@Injectable()
export class AdminAuthGuard implements CanActivate {
  private readonly logger = new Logger(AdminAuthGuard.name);

  constructor(
    @Inject(JwtService)
    private readonly jwtService: JwtService,
    @Inject(ConfigService)
    private readonly configService: ConfigService,
    private readonly permissionsService: PermissionsService,
    private readonly reflector: Reflector,
  ) { }

  async canActivate(context: ExecutionContext) {
    const request = context.switchToHttp().getRequest<AdminAuthRequest>();

    const token = this.extractTokenFromHeader(request);
    if (!token) {
      this.logger.warn('No token provided', {
        hasAuthHeader: !!request.headers.authorization,
        authHeader: request.headers.authorization ? 'present' : 'missing',
        headers: Object.keys(request.headers),
      });
      throw new UnauthorizedException('No token provided');
    }

    try {
      // Get the JWT secret from config
      const secret = this.configService.getOrThrow<string>('JWT_SECRET');

      // Verify token with the secret
      const payload = await this.jwtService.verifyAsync<IAdminTokenPayload>(token, {
        secret,
      });

      if (!payload.adminId || !payload.restId) {
        this.logger.error('Invalid token payload structure', payload);
        throw new UnauthorizedException('Invalid token payload');
      }

      request['adminId'] = payload.adminId;
      request['restId'] = payload.restId;

      // check if the user has the required permissions
      const requiredPermissions =
        this.reflector.getAllAndOverride<string[]>(PERMISSIONS_KEY, [context.getHandler(), context.getClass()]) ?? [];

      if (!requiredPermissions || requiredPermissions.length === 0) {
        return true;
      }

      const adminPermission = await this.permissionsService.getAdminPermissions(payload.adminId, payload.restId);
      if (!adminPermission || !Array.isArray(adminPermission)) {
        this.logger.error('No permissions found', { adminId: payload.adminId, restId: payload.restId });
        throw new ForbiddenException('No permissions found');
      }

      const hasPermission = requiredPermissions.every(p => adminPermission.includes(p));

      if (!hasPermission) {
        this.logger.warn('Insufficient permissions', {
          adminId: payload.adminId,
          restId: payload.restId,
          required: requiredPermissions,
          has: adminPermission,
        });
        throw new ForbiddenException('You are not authorized to access this resource');
      }

      return true;
    } catch (err) {
      if (err instanceof ForbiddenException || err instanceof UnauthorizedException) {
        throw err;
      }
      const errorMessage = err instanceof Error ? err.message : 'Unknown error';
      const errorStack = err instanceof Error ? err.stack : undefined;
      this.logger.error('Token verification error in AdminAuthGuard', {
        error: errorMessage,
        stack: errorStack,
      });
      throw new UnauthorizedException('Invalid or expired token');
    }
  }

  private extractTokenFromHeader(request: Request): string | undefined {
    const authHeader = request.headers.authorization || request.headers['authorization'];
    if (!authHeader) {
      return undefined;
    }

    const [type, token] = authHeader.split(' ');
    if (type?.toLowerCase() !== 'bearer' || !token) {
      return undefined;
    }

    return token;
  }
}
