import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
  Logger,
} from '@nestjs/common';
import { Observable, throwError } from 'rxjs';
import { tap, catchError } from 'rxjs/operators';
import { Request, Response } from 'express';
import { LoggerService } from './logger.service';

export interface LoggingContext {
  requestId: string;
  method: string;
  url: string;
  userId?: string;
  userAgent?: string;
  ip: string;
  startTime: number;
  controller: string;
  handler: string;
}

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

  constructor(private readonly loggerService: LoggerService) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const loggingContext = this.createLoggingContext(context);
    
    // Log del inicio de la ejecución
    this.logRequestStart(loggingContext);

    return next.handle().pipe(
      tap((data) => {
        // Log de respuesta exitosa
        this.logRequestSuccess(loggingContext, data);
      }),
      catchError((error) => {
        // Log de error
        this.logRequestError(loggingContext, error);
        return throwError(() => error);
      })
    );
  }

  private createLoggingContext(context: ExecutionContext): LoggingContext {
    const request = context.switchToHttp().getRequest<Request & { user?: any }>();
    const startTime = Date.now();
    
    // Generar request ID si no existe
    const requestId = request.headers['x-request-id'] as string || 
                     `req_${startTime}_${Math.random().toString(36).substr(2, 9)}`;

    return {
      requestId,
      method: request.method,
      url: request.originalUrl || request.url,
      userId: request.user?.id,
      userAgent: request.get('User-Agent'),
      ip: this.getClientIp(request),
      startTime,
      controller: context.getClass().name,
      handler: context.getHandler().name,
    };
  }

  private logRequestStart(context: LoggingContext): void {
    this.logger.log(
      `🚀 [${context.requestId}] ${context.method} ${context.url} - ${context.controller}.${context.handler}`,
      {
        requestId: context.requestId,
        method: context.method,
        url: context.url,
        controller: context.controller,
        handler: context.handler,
        userId: context.userId,
        ip: context.ip,
        userAgent: context.userAgent,
        phase: 'start',
      }
    );

    // Log de performance para monitoreo
    this.loggerService.logPerformance(
      `${context.controller}.${context.handler}`,
      0,
      {
        requestId: context.requestId,
        method: context.method,
        url: context.url,
        phase: 'start',
        userId: context.userId,
      }
    );
  }

  private logRequestSuccess(context: LoggingContext, data: any): void {
    const duration = Date.now() - context.startTime;
    const dataSize = this.calculateDataSize(data);

    this.logger.log(
      `✅ [${context.requestId}] ${context.method} ${context.url} - Completed in ${duration}ms`,
      {
        requestId: context.requestId,
        method: context.method,
        url: context.url,
        controller: context.controller,
        handler: context.handler,
        duration,
        dataSize,
        userId: context.userId,
        phase: 'success',
      }
    );

    // Log de performance
    this.loggerService.logPerformance(
      `${context.controller}.${context.handler}`,
      duration,
      {
        requestId: context.requestId,
        method: context.method,
        url: context.url,
        status: 'success',
        dataSize,
        userId: context.userId,
      }
    );

    // Alertar si la operación es muy lenta
    if (duration > 5000) { // 5 segundos
      this.logger.warn(
        `🐌 [${context.requestId}] Slow operation detected: ${duration}ms`,
        {
          requestId: context.requestId,
          controller: context.controller,
          handler: context.handler,
          duration,
          threshold: 5000,
        }
      );

      this.loggerService.logSecurity(
        'slow_operation_detected',
        'medium',
        {
          requestId: context.requestId,
          controller: context.controller,
          handler: context.handler,
          duration,
          url: context.url,
          userId: context.userId,
        }
      );
    }
  }

  private logRequestError(context: LoggingContext, error: any): void {
    const duration = Date.now() - context.startTime;
    const errorInfo = this.extractErrorInfo(error);

    this.logger.error(
      `❌ [${context.requestId}] ${context.method} ${context.url} - Failed in ${duration}ms: ${errorInfo.message}`,
      {
        requestId: context.requestId,
        method: context.method,
        url: context.url,
        controller: context.controller,
        handler: context.handler,
        duration,
        error: errorInfo,
        userId: context.userId,
        phase: 'error',
      }
    );

    // Log de performance para errores
    this.loggerService.logPerformance(
      `${context.controller}.${context.handler}`,
      duration,
      {
        requestId: context.requestId,
        method: context.method,
        url: context.url,
        status: 'error',
        errorType: errorInfo.type,
        errorCode: errorInfo.code,
        userId: context.userId,
      }
    );

    // Log de seguridad para ciertos tipos de errores
    if (this.isSecurityRelevantError(error)) {
      this.loggerService.logSecurity(
        'application_error',
        this.getErrorSeverity(error),
        {
          requestId: context.requestId,
          controller: context.controller,
          handler: context.handler,
          error: errorInfo,
          url: context.url,
          userId: context.userId,
        }
      );
    }
  }

  private getClientIp(request: Request): string {
    return (
      (request.headers['x-forwarded-for'] as string)?.split(',')[0] ||
      request.headers['x-real-ip'] ||
      request.connection?.remoteAddress ||
      request.socket?.remoteAddress ||
      'unknown'
    ) as string;
  }

  private calculateDataSize(data: any): number {
    if (!data) return 0;
    
    try {
      return JSON.stringify(data).length;
    } catch {
      return 0;
    }
  }

  private extractErrorInfo(error: any): {
    message: string;
    type: string;
    code?: string | number;
    stack?: string;
  } {
    return {
      message: error.message || 'Unknown error',
      type: error.constructor?.name || 'Error',
      code: error.code || error.status || error.statusCode,
      stack: error.stack,
    };
  }

  private isSecurityRelevantError(error: any): boolean {
    // Errores relacionados con autenticación y autorización
    const securityErrorCodes = [401, 403, 429];
    const securityErrorTypes = [
      'UnauthorizedException',
      'ForbiddenException',
      'ThrottlerException',
      'JwtException',
      'AuthenticationError',
      'AuthorizationError',
    ];

    const errorCode = error.status || error.statusCode || error.code;
    const errorType = error.constructor?.name;

    return (
      securityErrorCodes.includes(errorCode) ||
      securityErrorTypes.includes(errorType)
    );
  }

  private getErrorSeverity(error: any): 'low' | 'medium' | 'high' | 'critical' {
    const errorCode = error.status || error.statusCode || error.code;
    const errorType = error.constructor?.name;

    // Errores críticos
    if (errorCode >= 500 || errorType === 'InternalServerErrorException') {
      return 'critical';
    }

    // Errores de seguridad
    if (errorCode === 401 || errorCode === 403) {
      return 'high';
    }

    // Rate limiting
    if (errorCode === 429) {
      return 'medium';
    }

    // Otros errores de cliente
    if (errorCode >= 400 && errorCode < 500) {
      return 'low';
    }

    return 'medium';
  }
}

// Decorator para excluir endpoints del logging
export const NoLogging = () => {
  return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
    Reflect.defineMetadata('no-logging', true, descriptor.value);
  };
};

// Decorator para logging personalizado
export const CustomLogging = (options: {
  logRequest?: boolean;
  logResponse?: boolean;
  logErrors?: boolean;
  sensitiveFields?: string[];
}) => {
  return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
    Reflect.defineMetadata('custom-logging', options, descriptor.value);
  };
};