import { Injectable, CanActivate, ExecutionContext, UnauthorizedException, ForbiddenException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ConfigService } from '@nestjs/config';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom } from 'rxjs';

/**
 * IAM Adapter for @tria-plc corporate identity integration
 * 
 * This adapter wraps the corporate IAM guards and provides a bridge
 * between the corporate identity system and the EDR passenger API.
 * 
 * For back-office roles (agent, supervisor, admin, staff), this guard
 * validates tokens against the corporate IAM service.
 * 
 * For passenger-facing routes, the existing JWT guard is used.
 */

export interface IamTokenPayload {
  sub: string;
  email: string;
  roles: string[];
  permissions: string[];
  organizationId?: string;
  exp: number;
  iat: number;
}

export interface IamValidationResponse {
  valid: boolean;
  payload?: IamTokenPayload;
  error?: string;
}

@Injectable()
export class IamGuard implements CanActivate {
  private readonly iamApiUrl: string;
  private readonly iamEnabled: boolean;

  constructor(
    private readonly reflector: Reflector,
    private readonly config: ConfigService,
    private readonly http: HttpService,
  ) {
    this.iamApiUrl = this.config.get<string>('IAM_API_URL') || 'https://iam.tria-plc.com/api';
    this.iamEnabled = this.config.get<string>('IAM_ENABLED') === 'true';
  }

  async canActivate(context: ExecutionContext): Promise<boolean> {
    if (!this.iamEnabled) {
      // IAM disabled - allow access (for development)
      return true;
    }

    const request = context.switchToHttp().getRequest();
    const token = this.extractToken(request);

    if (!token) {
      throw new UnauthorizedException('No authentication token provided');
    }

    const validation = await this.validateToken(token);

    if (!validation.valid || !validation.payload) {
      throw new UnauthorizedException(validation.error || 'Invalid token');
    }

    // Check required roles
    const requiredRoles = this.reflector.get<string[]>('roles', context.getHandler());
    if (requiredRoles && requiredRoles.length > 0) {
      const hasRole = requiredRoles.some((role) => validation.payload!.roles.includes(role));
      if (!hasRole) {
        throw new ForbiddenException('Insufficient permissions');
      }
    }

    // Attach user to request
    request.user = {
      userId: validation.payload.sub,
      email: validation.payload.email,
      roles: validation.payload.roles,
      permissions: validation.payload.permissions,
      organizationId: validation.payload.organizationId,
    };

    return true;
  }

  private extractToken(request: any): string | null {
    const authHeader = request.headers.authorization;
    if (!authHeader) return null;

    const parts = authHeader.split(' ');
    if (parts.length !== 2 || parts[0] !== 'Bearer') return null;

    return parts[1];
  }

  private async validateToken(token: string): Promise<IamValidationResponse> {
    try {
      const response = await firstValueFrom(
        this.http.post<IamValidationResponse>(
          `${this.iamApiUrl}/v1/auth/validate`,
          { token },
          {
            headers: {
              'Content-Type': 'application/json',
              'X-API-Key': this.config.get<string>('IAM_API_KEY') || '',
            },
            timeout: 5000,
          },
        ),
      );

      return response.data;
    } catch (err) {
      return {
        valid: false,
        error: err instanceof Error ? err.message : 'Token validation failed',
      };
    }
  }
}

/**
 * Decorator to mark routes as requiring IAM authentication
 */
export const UseIamAuth = () => {
  // This is a marker decorator that can be used with @UseGuards(IamGuard)
  return (target: any, propertyKey?: string, descriptor?: PropertyDescriptor) => {
    // Marker only - actual guard is applied via @UseGuards
  };
};

/**
 * Decorator to specify required roles for IAM-protected routes
 */
export const IamRoles = (...roles: string[]) => {
  return (target: any, propertyKey?: string, descriptor?: PropertyDescriptor) => {
    if (descriptor) {
      Reflect.defineMetadata('roles', roles, descriptor.value);
    }
  };
};
