import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { LoggerService } from './logger.service';

export interface AuditableRequest extends Request {
  user?: {
    id: string;
    email?: string;
    role?: string;
    [key: string]: any;
  };
  auditContext?: {
    action?: string;
    resource?: string;
    resourceId?: string;
    metadata?: any;
  };
}

@Injectable()
export class AuditMiddleware implements NestMiddleware {
  constructor(private readonly logger: LoggerService) {}

  use(req: AuditableRequest, res: Response, next: NextFunction): void {
    const startTime = Date.now();
    const requestId = this.generateRequestId();
    
    // Agregar request ID a los headers
    req.headers['x-request-id'] = requestId;
    res.setHeader('x-request-id', requestId);

    // Capturar información de la request
    const requestInfo = {
      requestId,
      method: req.method,
      url: req.originalUrl || req.url,
      userAgent: req.get('User-Agent'),
      ip: this.getClientIp(req),
      userId: req.user?.id,
      userEmail: req.user?.email,
      timestamp: new Date().toISOString(),
    };

    // Log de inicio de request
    this.logger.logHttpRequest(
      req.method,
      req.originalUrl || req.url,
      0, // Status code será actualizado en la respuesta
      0, // Duration será calculado en la respuesta
      req.user?.id,
      {
        requestId,
        userAgent: req.get('User-Agent'),
        ip: this.getClientIp(req),
        phase: 'start',
      }
    );

    // Interceptar la respuesta
    const originalSend = res.send;
    const originalJson = res.json;
    let responseBody: any;

    res.send = function(body: any) {
      responseBody = body;
      return originalSend.call(this, body);
    };

    res.json = function(body: any) {
      responseBody = body;
      return originalJson.call(this, body);
    };

    // Manejar el final de la respuesta
    res.on('finish', () => {
      const duration = Date.now() - startTime;
      const statusCode = res.statusCode;

      // Log de finalización de request
      this.logger.logHttpRequest(
        req.method,
        req.originalUrl || req.url,
        statusCode,
        duration,
        req.user?.id,
        {
          requestId,
          userAgent: req.get('User-Agent'),
          ip: this.getClientIp(req),
          phase: 'end',
          contentLength: res.get('content-length'),
        }
      );

      // Log de auditoría para operaciones importantes
      if (this.shouldAudit(req, statusCode)) {
        this.logAuditEvent(req, res, duration, responseBody);
      }

      // Log de seguridad para eventos sospechosos
      if (this.isSuspiciousActivity(req, statusCode, duration)) {
        this.logSecurityEvent(req, res, duration);
      }
    });

    next();
  }

  private generateRequestId(): string {
    return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }

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

  private shouldAudit(req: AuditableRequest, statusCode: number): boolean {
    // Auditar operaciones de escritura exitosas
    const writeMethods = ['POST', 'PUT', 'PATCH', 'DELETE'];
    const isWriteOperation = writeMethods.includes(req.method);
    const isSuccessful = statusCode >= 200 && statusCode < 300;

    // Auditar rutas específicas
    const auditPaths = [
      '/auth/login',
      '/auth/logout',
      '/auth/register',
      '/users',
      '/admin',
    ];
    const isAuditablePath = auditPaths.some(path => 
      req.originalUrl?.startsWith(path) || req.url?.startsWith(path)
    );

    return (isWriteOperation && isSuccessful) || isAuditablePath;
  }

  private isSuspiciousActivity(
    req: AuditableRequest,
    statusCode: number,
    duration: number
  ): boolean {
    // Detectar intentos de acceso no autorizado
    if (statusCode === 401 || statusCode === 403) {
      return true;
    }

    // Detectar requests muy lentas (posible DoS)
    if (duration > 10000) { // 10 segundos
      return true;
    }

    // Detectar múltiples errores 4xx
    if (statusCode >= 400 && statusCode < 500) {
      return true;
    }

    // Detectar rutas de administración sin autenticación
    const adminPaths = ['/admin', '/api/admin'];
    const isAdminPath = adminPaths.some(path => 
      req.originalUrl?.startsWith(path) || req.url?.startsWith(path)
    );
    
    if (isAdminPath && !req.user) {
      return true;
    }

    return false;
  }

  private logAuditEvent(
    req: AuditableRequest,
    res: Response,
    duration: number,
    responseBody: any
  ): void {
    const action = this.determineAction(req);
    const resource = this.determineResource(req);
    const resourceId = this.extractResourceId(req);

    this.logger.logAudit(
      action,
      req.user?.id,
      resource,
      {
        resourceId,
        method: req.method,
        url: req.originalUrl || req.url,
        statusCode: res.statusCode,
        duration,
        ip: this.getClientIp(req),
        userAgent: req.get('User-Agent'),
        requestBody: this.sanitizeRequestBody(req.body),
        responseStatus: res.statusCode,
        auditContext: req.auditContext,
      }
    );
  }

  private logSecurityEvent(
    req: AuditableRequest,
    res: Response,
    duration: number
  ): void {
    let severity: 'low' | 'medium' | 'high' | 'critical' = 'low';
    let event = 'suspicious_activity';

    if (res.statusCode === 401 || res.statusCode === 403) {
      severity = 'medium';
      event = 'unauthorized_access_attempt';
    }

    if (duration > 10000) {
      severity = 'high';
      event = 'slow_request_detected';
    }

    const adminPaths = ['/admin', '/api/admin'];
    const isAdminPath = adminPaths.some(path => 
      req.originalUrl?.startsWith(path) || req.url?.startsWith(path)
    );
    
    if (isAdminPath && !req.user) {
      severity = 'high';
      event = 'unauthorized_admin_access_attempt';
    }

    this.logger.logSecurity(event, severity, {
      method: req.method,
      url: req.originalUrl || req.url,
      statusCode: res.statusCode,
      duration,
      ip: this.getClientIp(req),
      userAgent: req.get('User-Agent'),
      userId: req.user?.id,
    });
  }

  private determineAction(req: AuditableRequest): string {
    if (req.auditContext?.action) {
      return req.auditContext.action;
    }

    const methodActions: Record<string, string> = {
      GET: 'read',
      POST: 'create',
      PUT: 'update',
      PATCH: 'update',
      DELETE: 'delete',
    };

    return methodActions[req.method] || 'unknown';
  }

  private determineResource(req: AuditableRequest): string {
    if (req.auditContext?.resource) {
      return req.auditContext.resource;
    }

    const url = req.originalUrl || req.url;
    const pathSegments = url.split('/').filter(segment => segment && segment !== 'api');
    
    return pathSegments[0] || 'unknown';
  }

  private extractResourceId(req: AuditableRequest): string | undefined {
    if (req.auditContext?.resourceId) {
      return req.auditContext.resourceId;
    }

    // Intentar extraer ID de los parámetros de la URL
    const url = req.originalUrl || req.url;
    const uuidRegex = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
    const match = url.match(uuidRegex);
    
    return match ? match[0] : undefined;
  }

  private sanitizeRequestBody(body: any): any {
    if (!body || typeof body !== 'object') {
      return body;
    }

    const sensitiveFields = [
      'password',
      'token',
      'secret',
      'key',
      'authorization',
      'credit_card',
      'ssn',
    ];

    const sanitized = { ...body };
    
    for (const field of sensitiveFields) {
      if (sanitized[field]) {
        sanitized[field] = '[REDACTED]';
      }
    }

    return sanitized;
  }
}