import { Injectable, Inject } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { Logger } from "nestjs-pino";

export interface ErrorContext {
  userId?: string;
  requestId?: string;
  userAgent?: string;
  ip?: string;
  url?: string;
  method?: string;
}

export interface SecureErrorMapping {
  code: string;
  publicMessage: string;
  logLevel: "error" | "warn" | "info" | "debug";
  shouldAlert?: boolean; // Whether to send alerts to monitoring
}

export interface ErrorClassification {
  category: "authentication" | "authorization" | "validation" | "business" | "system" | "external";
  severity: "low" | "medium" | "high" | "critical";
  mapping: SecureErrorMapping;
}

/**
 * Service for secure error message mapping to prevent information leakage
 * Maps internal errors to safe public messages while preserving security
 */
@Injectable()
export class SecureErrorMapperService {
  private readonly isDevelopment: boolean;
  private readonly errorMappings: Map<string, SecureErrorMapping>;
  private readonly patternMappings: Array<{ pattern: RegExp; mapping: SecureErrorMapping }>;

  constructor(
    private readonly configService: ConfigService,
    @Inject(Logger) private readonly logger: Logger
  ) {
    this.isDevelopment = this.configService.get("NODE_ENV") !== "production";
    this.errorMappings = this.initializeErrorMappings();
    this.patternMappings = this.initializePatternMappings();
  }

  /**
   * Map an error to a secure public message
   */
  mapError(error: unknown, context?: ErrorContext): ErrorClassification {
    const errorMessage = this.extractErrorMessage(error);
    const errorCode = this.extractErrorCode(error);

    // Try exact code mapping first
    if (errorCode && this.errorMappings.has(errorCode)) {
      const mapping = this.errorMappings.get(errorCode)!;
      return this.createClassification(errorMessage, mapping, context);
    }

    // Try pattern matching
    for (const { pattern, mapping } of this.patternMappings) {
      if (pattern.test(errorMessage)) {
        return this.createClassification(errorMessage, mapping, context);
      }
    }

    // Default fallback
    const defaultMapping = this.getDefaultMapping(errorMessage);
    return this.createClassification(errorMessage, defaultMapping, context);
  }

  /**
   * Get a safe error message for client consumption
   */
  getPublicMessage(error: unknown, context?: ErrorContext): string {
    const classification = this.mapError(error, context);

    // In development, show more details
    if (this.isDevelopment) {
      const originalMessage = this.extractErrorMessage(error);
      return `${classification.mapping.publicMessage} (Dev: ${this.sanitizeForDevelopment(originalMessage)})`;
    }

    return classification.mapping.publicMessage;
  }

  /**
   * Log error with appropriate security level
   */
  logSecureError(
    error: unknown,
    context?: ErrorContext,
    additionalData?: Record<string, unknown>
  ): void {
    const classification = this.mapError(error, context);
    const originalMessage = this.extractErrorMessage(error);

    const logData = {
      errorCode: classification.mapping.code,
      category: classification.category,
      severity: classification.severity,
      publicMessage: classification.mapping.publicMessage,
      originalMessage: this.sanitizeForLogging(originalMessage),
      context,
      ...additionalData,
    };

    // Log based on severity and log level
    switch (classification.mapping.logLevel) {
      case "error":
        this.logger.error(`Security Error: ${classification.mapping.code}`, logData);
        break;
      case "warn":
        this.logger.warn(`Security Warning: ${classification.mapping.code}`, logData);
        break;
      case "info":
        this.logger.log(`Security Info: ${classification.mapping.code}`, logData);
        break;
      case "debug":
        this.logger.debug(`Security Debug: ${classification.mapping.code}`, logData);
        break;
    }

    // Send alerts for critical errors
    if (classification.mapping.shouldAlert && classification.severity === "critical") {
      this.sendSecurityAlert(classification, context, logData);
    }
  }

  private initializeErrorMappings(): Map<string, SecureErrorMapping> {
    return new Map([
      // Authentication Errors
      [
        "INVALID_CREDENTIALS",
        {
          code: "AUTH_001",
          publicMessage: "Invalid email or password",
          logLevel: "warn",
        },
      ],
      [
        "ACCOUNT_LOCKED",
        {
          code: "AUTH_002",
          publicMessage: "Account temporarily locked. Please try again later",
          logLevel: "warn",
        },
      ],
      [
        "TOKEN_EXPIRED",
        {
          code: "AUTH_003",
          publicMessage: "Session expired. Please log in again",
          logLevel: "info",
        },
      ],
      [
        "TOKEN_INVALID",
        {
          code: "AUTH_004",
          publicMessage: "Invalid session. Please log in again",
          logLevel: "warn",
        },
      ],
      [
        "SESSION_EXPIRED",
        {
          code: "SESSION_EXPIRED",
          publicMessage: "Your session has expired. Please log in again",
          logLevel: "info",
        },
      ],

      // Authorization Errors
      [
        "INSUFFICIENT_PERMISSIONS",
        {
          code: "AUTHZ_001",
          publicMessage: "You do not have permission to perform this action",
          logLevel: "warn",
        },
      ],
      [
        "RESOURCE_NOT_FOUND",
        {
          code: "AUTHZ_002",
          publicMessage: "The requested resource was not found",
          logLevel: "info",
        },
      ],

      // Validation Errors
      [
        "VALIDATION_FAILED",
        {
          code: "VAL_001",
          publicMessage: "The provided data is invalid",
          logLevel: "info",
        },
      ],
      [
        "REQUIRED_FIELD_MISSING",
        {
          code: "VAL_002",
          publicMessage: "Required information is missing",
          logLevel: "info",
        },
      ],

      // Business Logic Errors
      [
        "ORDER_ALREADY_PROCESSED",
        {
          code: "BIZ_001",
          publicMessage: "This order has already been processed",
          logLevel: "info",
        },
      ],
      [
        "INSUFFICIENT_BALANCE",
        {
          code: "BIZ_002",
          publicMessage: "Insufficient account balance",
          logLevel: "info",
        },
      ],
      [
        "SERVICE_UNAVAILABLE",
        {
          code: "BIZ_003",
          publicMessage: "Service is temporarily unavailable",
          logLevel: "warn",
        },
      ],

      // System Errors (High Security)
      [
        "DATABASE_ERROR",
        {
          code: "SYS_001",
          publicMessage: "A system error occurred. Please try again later",
          logLevel: "error",
          shouldAlert: true,
        },
      ],
      [
        "EXTERNAL_SERVICE_ERROR",
        {
          code: "SYS_002",
          publicMessage: "External service temporarily unavailable",
          logLevel: "error",
        },
      ],
      [
        "CONFIGURATION_ERROR",
        {
          code: "SYS_003",
          publicMessage: "System configuration error",
          logLevel: "error",
          shouldAlert: true,
        },
      ],

      // Rate Limiting
      [
        "RATE_LIMIT_EXCEEDED",
        {
          code: "RATE_001",
          publicMessage: "Too many requests. Please try again later",
          logLevel: "warn",
        },
      ],

      // Generic Fallbacks
      [
        "UNKNOWN_ERROR",
        {
          code: "GEN_001",
          publicMessage: "An unexpected error occurred",
          logLevel: "error",
          shouldAlert: true,
        },
      ],
    ]);
  }

  private initializePatternMappings(): Array<{ pattern: RegExp; mapping: SecureErrorMapping }> {
    return [
      // Database-related patterns
      {
        pattern: /database|connection|sql|prisma|postgres/i,
        mapping: {
          code: "SYS_001",
          publicMessage: "A system error occurred. Please try again later",
          logLevel: "error",
          shouldAlert: true,
        },
      },

      // Authentication patterns
      {
        pattern: /token expired or expiring soon/i,
        mapping: {
          code: "SESSION_EXPIRED",
          publicMessage: "Your session has expired. Please log in again",
          logLevel: "info",
        },
      },
      {
        pattern: /password|credential|token|secret|key|auth/i,
        mapping: {
          code: "AUTH_001",
          publicMessage: "Authentication failed",
          logLevel: "warn",
        },
      },

      // File system patterns
      {
        pattern: /file|path|directory|permission denied|enoent|eacces/i,
        mapping: {
          code: "SYS_002",
          publicMessage: "System resource error",
          logLevel: "error",
          shouldAlert: true,
        },
      },

      // Network/External service patterns
      {
        pattern: /network|timeout|connection refused|econnrefused|whmcs|salesforce/i,
        mapping: {
          code: "SYS_002",
          publicMessage: "External service temporarily unavailable",
          logLevel: "error",
        },
      },

      // Stack trace patterns
      {
        pattern: /\s+at\s+|\.js:\d+|\.ts:\d+|stack trace/i,
        mapping: {
          code: "SYS_001",
          publicMessage: "A system error occurred. Please try again later",
          logLevel: "error",
          shouldAlert: true,
        },
      },

      // Memory/Resource patterns
      {
        pattern: /memory|heap|out of memory|resource|limit exceeded/i,
        mapping: {
          code: "SYS_003",
          publicMessage: "System resources temporarily unavailable",
          logLevel: "error",
          shouldAlert: true,
        },
      },

      // Validation patterns
      {
        pattern: /invalid|required|missing|validation|format/i,
        mapping: {
          code: "VAL_001",
          publicMessage: "The provided data is invalid",
          logLevel: "info",
        },
      },
    ];
  }

  private createClassification(
    originalMessage: string,
    mapping: SecureErrorMapping,
    _context?: ErrorContext
  ): ErrorClassification {
    // Determine category and severity based on error code
    const category = this.determineCategory(mapping.code);
    const severity = this.determineSeverity(mapping.code, originalMessage);

    return {
      category,
      severity,
      mapping,
    };
  }

  private determineCategory(code: string): ErrorClassification["category"] {
    if (code === "SESSION_EXPIRED") return "authentication";
    if (code.startsWith("AUTH_")) return "authentication";
    if (code.startsWith("AUTHZ_")) return "authorization";
    if (code.startsWith("VAL_")) return "validation";
    if (code.startsWith("BIZ_")) return "business";
    if (code.startsWith("SYS_")) return "system";
    return "system";
  }

  private determineSeverity(code: string, message: string): ErrorClassification["severity"] {
    // Critical system errors
    if (code === "SYS_001" || code === "SYS_003") return "critical";

    // High severity for authentication issues
    if (code === "SESSION_EXPIRED") return "medium";
    if (code.startsWith("AUTH_") && message.toLowerCase().includes("breach")) return "high";

    // Medium for external service issues
    if (code === "SYS_002") return "medium";

    // Low for validation and business logic
    if (code.startsWith("VAL_") || code.startsWith("BIZ_")) return "low";

    return "medium";
  }

  private getDefaultMapping(message: string): SecureErrorMapping {
    // Analyze message for sensitivity
    if (this.containsSensitiveInfo(message)) {
      return {
        code: "SYS_001",
        publicMessage: "A system error occurred. Please try again later",
        logLevel: "error",
        shouldAlert: true,
      };
    }

    return {
      code: "GEN_001",
      publicMessage: "An unexpected error occurred",
      logLevel: "error",
    };
  }

  private containsSensitiveInfo(message: string): boolean {
    const sensitivePatterns = [
      /password|secret|key|token|credential/i,
      /database|sql|connection/i,
      /file|path|directory/i,
      /\s+at\s+.*\.js:\d+/i, // Stack traces
      /[a-zA-Z]:[\\/]/, // Windows paths
      /\/[a-zA-Z0-9._\-/]+\.(js|ts|py|php)/i, // Unix paths
      /\b(?:\d{1,3}\.){3}\d{1,3}\b/, // IP addresses
      /[A-Za-z0-9]{32,}/, // Long tokens/hashes
    ];

    return sensitivePatterns.some(pattern => pattern.test(message));
  }

  private extractErrorMessage(error: unknown): string {
    if (error instanceof Error) {
      return error.message;
    }
    if (typeof error === "string") {
      return error;
    }
    if (typeof error === "object" && error !== null) {
      const obj = error as Record<string, unknown>;
      if (typeof obj.message === "string") {
        return obj.message;
      }
    }
    return "Unknown error";
  }

  private extractErrorCode(error: unknown): string | null {
    if (typeof error === "object" && error !== null) {
      const obj = error as Record<string, unknown>;
      if (typeof obj.code === "string") {
        return obj.code;
      }
    }
    return null;
  }

  private sanitizeForLogging(message: string): string {
    return (
      message
        // Remove file paths
        .replace(/[/][a-zA-Z0-9._\-/]+\.(js|ts|py|php)/g, "[file]")
        // Remove stack traces
        .replace(/\s+at\s+.*/g, "")
        // Remove absolute paths
        .replace(/[a-zA-Z]:[\\/][^:]+/g, "[path]")
        // Remove IP addresses
        .replace(/\b(?:\d{1,3}\.){3}\d{1,3}\b/g, "[ip]")
        // Remove URLs with credentials
        .replace(/https?:\/\/[^:]+:[^@]+@[^\s]+/g, "[url]")
        // Remove potential secrets
        .replace(/\b[A-Za-z0-9]{32,}\b/g, "[token]")
        .trim()
    );
  }

  private sanitizeForDevelopment(message: string): string {
    // In development, show more but still remove the most sensitive parts
    return message
      .replace(/password[=:]\s*[^\s]+/gi, "password=[HIDDEN]")
      .replace(/secret[=:]\s*[^\s]+/gi, "secret=[HIDDEN]")
      .replace(/token[=:]\s*[^\s]+/gi, "token=[HIDDEN]")
      .replace(/key[=:]\s*[^\s]+/gi, "key=[HIDDEN]");
  }

  private sendSecurityAlert(
    classification: ErrorClassification,
    context?: ErrorContext,
    logData?: Record<string, unknown>
  ): void {
    // In a real implementation, this would send alerts to monitoring systems
    // like Slack, PagerDuty, or custom alerting systems
    this.logger.error("SECURITY ALERT TRIGGERED", {
      alertType: "CRITICAL_ERROR",
      errorCode: classification.mapping.code,
      category: classification.category,
      severity: classification.severity,
      context,
      timestamp: new Date().toISOString(),
      ...logData,
    });
  }
}
