import { ErrorSeverity, ErrorCategory, ErrorRecoverability } from './error.codes.js';
import { logger } from '../logger.js';

// Import TLogLevel from plugins
import type { TLogLevel } from '../plugins.js';

/**
 * Context information added to structured errors
 */
export interface IErrorContext {
  /** Component or service where the error occurred */
  component?: string;
  
  /** Operation that was being performed */
  operation?: string;
  
  /** Unique request ID if available */
  requestId?: string;
  
  /** Error occurred at timestamp */
  timestamp?: number;
  
  /** User-visible message (safe to display to end-users) */
  userMessage?: string;
  
  /** Additional structured data for debugging */
  data?: Record<string, any>;
  
  /** Related entity IDs if applicable */
  entity?: { 
    type: string; 
    id: string | number;
  };
  
  /** Stack trace (if enabled in configuration) */
  stack?: string;
  
  /** Retry information if applicable */
  retry?: {
    /** Maximum number of retries allowed */
    maxRetries?: number;
    
    /** Current retry count */
    currentRetry?: number;
    
    /** Next retry timestamp */
    nextRetryAt?: number;
    
    /** Delay between retries (in ms) */
    retryDelay?: number;
  };
}

/**
 * Base class for all errors in the Platform Service
 * Adds structured error information, logging, and error tracking
 */
export class PlatformError extends Error {
  /** Error code identifying the specific error type */
  public readonly code: string;
  
  /** Error severity level */
  public readonly severity: ErrorSeverity;
  
  /** Error category for grouping related errors */
  public readonly category: ErrorCategory;
  
  /** Whether the error can be recovered from automatically */
  public readonly recoverability: ErrorRecoverability;
  
  /** Additional context information */
  public readonly context: IErrorContext;

  /**
   * Creates a new PlatformError
   * 
   * @param message Error message
   * @param code Error code from error.codes.ts
   * @param severity Error severity level
   * @param category Error category
   * @param recoverability Error recoverability indication
   * @param context Additional context information
   */
  constructor(
    message: string,
    code: string,
    severity: ErrorSeverity = ErrorSeverity.MEDIUM,
    category: ErrorCategory = ErrorCategory.OTHER,
    recoverability: ErrorRecoverability = ErrorRecoverability.NON_RECOVERABLE,
    context: IErrorContext = {}
  ) {
    super(message);

    // Set error metadata
    this.name = this.constructor.name;
    this.code = code;
    this.severity = severity;
    this.category = category;
    this.recoverability = recoverability;
    
    // Add timestamp if not provided
    this.context = {
      ...context,
      timestamp: context.timestamp || Date.now(),
    };

    // Capture stack trace
    Error.captureStackTrace(this, this.constructor);
    
    // Log the error automatically unless explicitly disabled
    if (!context.data?.skipLogging) {
      this.logError();
    }
  }

  /**
   * Logs the error using the platform logger
   */
  private logError(): void {
    const logLevel = this.getLogLevelFromSeverity() as TLogLevel;
    
    // Construct structured log entry
    const logData = {
      error_code: this.code,
      error_name: this.name,
      severity: this.severity,
      category: this.category,
      recoverability: this.recoverability,
      ...this.context
    };
    
    // Log with appropriate level
    logger.log(logLevel, this.message, logData);
  }

  /**
   * Maps severity levels to log levels
   */
  private getLogLevelFromSeverity(): string {
    switch (this.severity) {
      case ErrorSeverity.CRITICAL:
      case ErrorSeverity.HIGH:
        return 'error';
      case ErrorSeverity.MEDIUM:
        return 'warn';
      case ErrorSeverity.LOW:
        return 'info';
      case ErrorSeverity.INFO:
        return 'debug';
      default:
        return 'error';
    }
  }

  /**
   * Returns a JSON representation of the error
   */
  public toJSON(): Record<string, any> {
    return {
      name: this.name,
      message: this.message,
      code: this.code,
      severity: this.severity,
      category: this.category,
      recoverability: this.recoverability,
      context: this.context,
      stack: process.env.NODE_ENV !== 'production' ? this.stack : undefined
    };
  }

  /**
   * Creates an instance with retry information
   * 
   * @param maxRetries Maximum number of retries
   * @param currentRetry Current retry count
   * @param retryDelay Delay between retries in ms
   */
  public withRetry(
    maxRetries: number,
    currentRetry: number = 0,
    retryDelay: number = 1000
  ): PlatformError {
    const nextRetryAt = Date.now() + retryDelay;
    
    // Clone the error with updated context
    const newContext = {
      ...this.context,
      retry: {
        maxRetries,
        currentRetry,
        nextRetryAt,
        retryDelay
      }
    };
    
    // Create a new instance using the protected method that subclasses can override
    const newError = this.createWithContext(newContext);
    
    // Update recoverability if we can retry
    if (currentRetry < maxRetries && newError.recoverability === ErrorRecoverability.NON_RECOVERABLE) {
      (newError as any).recoverability = ErrorRecoverability.MAYBE_RECOVERABLE;
    }
    
    return newError;
  }
  
  /**
   * Protected method to create a new instance with updated context
   * Subclasses can override this to handle their own constructor signatures
   */
  protected createWithContext(context: IErrorContext): PlatformError {
    // Default implementation for PlatformError
    return new (this.constructor as typeof PlatformError)(
      this.message,
      this.code,
      this.severity,
      this.category,
      this.recoverability,
      context
    );
  }

  /**
   * Checks if the error should be retried based on retry information
   */
  public shouldRetry(): boolean {
    const { retry } = this.context;
    if (!retry) return false;
    
    return (retry.currentRetry ?? 0) < (retry.maxRetries ?? 0);
  }

  /**
   * Returns a user-friendly message that is safe to display to end users
   */
  public getUserMessage(): string {
    return this.context.userMessage || 'An unexpected error occurred.';
  }
}

/**
 * Error class for validation errors
 */
export class ValidationError extends PlatformError {
  /**
   * Creates a new validation error
   * 
   * @param message Error message
   * @param code Error code
   * @param context Additional context
   */
  constructor(
    message: string,
    code: string,
    context: IErrorContext = {}
  ) {
    super(
      message,
      code,
      ErrorSeverity.LOW,
      ErrorCategory.VALIDATION,
      ErrorRecoverability.NON_RECOVERABLE,
      context
    );
  }
  
  /**
   * Creates a new instance with updated context
   * Overrides the base implementation to handle ValidationError's constructor signature
   */
  protected createWithContext(context: IErrorContext): PlatformError {
    return new (this.constructor as typeof ValidationError)(
      this.message,
      this.code,
      context
    );
  }
}

/**
 * Error class for configuration errors
 */
export class ConfigurationError extends PlatformError {
  /**
   * Creates a new configuration error
   * 
   * @param message Error message
   * @param code Error code
   * @param context Additional context
   */
  constructor(
    message: string,
    code: string,
    context: IErrorContext = {}
  ) {
    super(
      message,
      code,
      ErrorSeverity.MEDIUM,
      ErrorCategory.CONFIGURATION,
      ErrorRecoverability.NON_RECOVERABLE,
      context
    );
  }
  
  /**
   * Creates a new instance with updated context
   * Overrides the base implementation to handle ConfigurationError's constructor signature
   */
  protected createWithContext(context: IErrorContext): PlatformError {
    return new (this.constructor as typeof ConfigurationError)(
      this.message,
      this.code,
      context
    );
  }
}

/**
 * Error class for network-related errors
 */
export class NetworkError extends PlatformError {
  /**
   * Creates a new network error
   * 
   * @param message Error message
   * @param code Error code
   * @param context Additional context
   */
  constructor(
    message: string,
    code: string,
    context: IErrorContext = {}
  ) {
    super(
      message,
      code,
      ErrorSeverity.MEDIUM,
      ErrorCategory.CONNECTIVITY,
      ErrorRecoverability.MAYBE_RECOVERABLE,
      context
    );
  }
  
  /**
   * Creates a new instance with updated context
   * Overrides the base implementation to handle NetworkError's constructor signature
   */
  protected createWithContext(context: IErrorContext): PlatformError {
    return new (this.constructor as typeof NetworkError)(
      this.message,
      this.code,
      context
    );
  }
}

/**
 * Error class for resource availability errors (rate limits, quotas)
 */
export class ResourceError extends PlatformError {
  /**
   * Creates a new resource error
   * 
   * @param message Error message
   * @param code Error code
   * @param context Additional context
   */
  constructor(
    message: string,
    code: string,
    context: IErrorContext = {}
  ) {
    super(
      message,
      code,
      ErrorSeverity.MEDIUM,
      ErrorCategory.RESOURCE,
      ErrorRecoverability.MAYBE_RECOVERABLE,
      context
    );
  }
  
  /**
   * Creates a new instance with updated context
   * Overrides the base implementation to handle ResourceError's constructor signature
   */
  protected createWithContext(context: IErrorContext): PlatformError {
    return new (this.constructor as typeof ResourceError)(
      this.message,
      this.code,
      context
    );
  }
}

/**
 * Error class for authentication/authorization errors
 */
export class AuthenticationError extends PlatformError {
  /**
   * Creates a new authentication error
   * 
   * @param message Error message
   * @param code Error code
   * @param context Additional context
   */
  constructor(
    message: string,
    code: string,
    context: IErrorContext = {}
  ) {
    super(
      message,
      code,
      ErrorSeverity.HIGH,
      ErrorCategory.AUTHENTICATION,
      ErrorRecoverability.NON_RECOVERABLE,
      context
    );
  }
  
  /**
   * Creates a new instance with updated context
   * Overrides the base implementation to handle AuthenticationError's constructor signature
   */
  protected createWithContext(context: IErrorContext): PlatformError {
    return new (this.constructor as typeof AuthenticationError)(
      this.message,
      this.code,
      context
    );
  }
}

/**
 * Error class for operation errors (API calls, processing)
 */
export class OperationError extends PlatformError {
  /**
   * Creates a new operation error
   * 
   * @param message Error message
   * @param code Error code
   * @param context Additional context
   */
  constructor(
    message: string,
    code: string,
    context: IErrorContext = {}
  ) {
    super(
      message,
      code,
      ErrorSeverity.MEDIUM,
      ErrorCategory.OPERATION,
      ErrorRecoverability.MAYBE_RECOVERABLE,
      context
    );
  }
  
  /**
   * Creates a new instance with updated context
   * Overrides the base implementation to handle OperationError's constructor signature
   */
  protected createWithContext(context: IErrorContext): PlatformError {
    return new (this.constructor as typeof OperationError)(
      this.message,
      this.code,
      context
    );
  }
}

/**
 * Error class for critical system errors
 */
export class SystemError extends PlatformError {
  /**
   * Creates a new system error
   * 
   * @param message Error message
   * @param code Error code
   * @param context Additional context
   */
  constructor(
    message: string,
    code: string,
    context: IErrorContext = {}
  ) {
    super(
      message,
      code,
      ErrorSeverity.CRITICAL,
      ErrorCategory.OTHER,
      ErrorRecoverability.NON_RECOVERABLE,
      context
    );
  }
}

/**
 * Helper to get the appropriate error class based on error category
 * 
 * @param category Error category
 * @returns The appropriate error class
 */
export function getErrorClassForCategory(category: ErrorCategory): any {
  switch (category) {
    case ErrorCategory.VALIDATION:
      return ValidationError;
    case ErrorCategory.CONFIGURATION:
      return ConfigurationError;
    case ErrorCategory.CONNECTIVITY:
      return NetworkError;
    case ErrorCategory.RESOURCE:
      return ResourceError;
    case ErrorCategory.AUTHENTICATION:
      return AuthenticationError;
    case ErrorCategory.OPERATION:
      return OperationError;
    default:
      return PlatformError;
  }
}