import { 
  PlatformError,
  NetworkError, 
  AuthenticationError, 
  OperationError,
  ConfigurationError
} from './base.errors.js';
import type { IErrorContext } from './base.errors.js';

import {
  MTA_CONNECTION_ERROR,
  MTA_AUTHENTICATION_ERROR,
  MTA_DELIVERY_ERROR,
  MTA_CONFIGURATION_ERROR,
  MTA_DNS_ERROR,
  MTA_TIMEOUT_ERROR,
  MTA_PROTOCOL_ERROR
} from './error.codes.js';

/**
 * Base class for MTA connection errors
 */
export class MtaConnectionError extends NetworkError {
  /**
   * Creates a new MTA connection error
   * 
   * @param message Error message
   * @param context Additional context
   */
  constructor(
    message: string,
    context: IErrorContext = {}
  ) {
    super(message, MTA_CONNECTION_ERROR, context);
  }

  /**
   * Creates an instance for a DNS resolution error
   * 
   * @param hostname Hostname that failed to resolve
   * @param originalError Original error
   * @param context Additional context
   */
  public static dnsError(
    hostname: string,
    originalError?: Error,
    context: IErrorContext = {}
  ): MtaConnectionError {
    const errorMsg = originalError ? `: ${originalError.message}` : '';
    return new MtaConnectionError(
      `Failed to resolve DNS for ${hostname}${errorMsg}`,
      {
        ...context,
        data: {
          ...context.data,
          hostname,
          originalError: originalError ? {
            message: originalError.message,
            stack: originalError.stack
          } : undefined
        },
        userMessage: `Could not connect to mail server for ${hostname}.`
      }
    );
  }

  /**
   * Creates an instance for a connection timeout
   * 
   * @param hostname Hostname that timed out
   * @param port Port number
   * @param timeout Timeout in milliseconds
   * @param context Additional context
   */
  public static timeout(
    hostname: string,
    port: number,
    timeout: number,
    context: IErrorContext = {}
  ): MtaConnectionError {
    return new MtaConnectionError(
      `Connection to ${hostname}:${port} timed out after ${timeout}ms`,
      {
        ...context,
        data: {
          ...context.data,
          hostname,
          port,
          timeout
        },
        userMessage: `Connection to mail server timed out.`
      }
    );
  }

  /**
   * Creates an instance for a connection refused error
   * 
   * @param hostname Hostname that refused connection
   * @param port Port number
   * @param context Additional context
   */
  public static refused(
    hostname: string,
    port: number,
    context: IErrorContext = {}
  ): MtaConnectionError {
    return new MtaConnectionError(
      `Connection to ${hostname}:${port} refused`,
      {
        ...context,
        data: {
          ...context.data,
          hostname,
          port
        },
        userMessage: `Connection to mail server was refused.`
      }
    );
  }
}

/**
 * Error class for MTA authentication errors
 */
export class MtaAuthenticationError extends AuthenticationError {
  /**
   * Creates a new MTA authentication error
   * 
   * @param message Error message
   * @param context Additional context
   */
  constructor(
    message: string,
    context: IErrorContext = {}
  ) {
    super(message, MTA_AUTHENTICATION_ERROR, context);
  }

  /**
   * Creates an instance for invalid credentials
   * 
   * @param hostname Hostname where authentication failed
   * @param username Username that failed authentication
   * @param context Additional context
   */
  public static invalidCredentials(
    hostname: string,
    username: string,
    context: IErrorContext = {}
  ): MtaAuthenticationError {
    return new MtaAuthenticationError(
      `Authentication failed for user ${username} at ${hostname}`,
      {
        ...context,
        data: {
          ...context.data,
          hostname,
          username
        },
        userMessage: `Authentication to mail server failed.`
      }
    );
  }

  /**
   * Creates an instance for unsupported authentication method
   * 
   * @param hostname Hostname
   * @param method Authentication method that is not supported
   * @param supportedMethods List of supported authentication methods
   * @param context Additional context
   */
  public static unsupportedMethod(
    hostname: string,
    method: string,
    supportedMethods: string[] = [],
    context: IErrorContext = {}
  ): MtaAuthenticationError {
    return new MtaAuthenticationError(
      `Authentication method ${method} not supported by ${hostname}${supportedMethods.length > 0 ? `. Supported methods: ${supportedMethods.join(', ')}` : ''}`,
      {
        ...context,
        data: {
          ...context.data,
          hostname,
          method,
          supportedMethods
        },
        userMessage: `The mail server doesn't support the required authentication method.`
      }
    );
  }
}

/**
 * Error class for MTA delivery errors
 */
export class MtaDeliveryError extends OperationError {
  /**
   * Creates a new MTA delivery error
   * 
   * @param message Error message
   * @param context Additional context
   */
  constructor(
    message: string,
    context: IErrorContext = {}
  ) {
    super(message, MTA_DELIVERY_ERROR, context);
  }

  /**
   * Creates an instance for a permanent delivery failure
   * 
   * @param message Error message
   * @param recipientAddress Recipient email address
   * @param statusCode SMTP status code
   * @param smtpResponse Full SMTP response
   * @param context Additional context
   */
  public static permanent(
    message: string,
    recipientAddress: string,
    statusCode?: string,
    smtpResponse?: string,
    context: IErrorContext = {}
  ): MtaDeliveryError {
    const statusCodeStr = statusCode ? ` (${statusCode})` : '';
    return new MtaDeliveryError(
      `Permanent delivery failure to ${recipientAddress}${statusCodeStr}: ${message}`,
      {
        ...context,
        data: {
          ...context.data,
          recipientAddress,
          statusCode,
          smtpResponse,
          permanent: true
        },
        userMessage: `The email could not be delivered to ${recipientAddress}.`
      }
    );
  }

  /**
   * Creates an instance for a temporary delivery failure
   * 
   * @param message Error message
   * @param recipientAddress Recipient email address
   * @param statusCode SMTP status code
   * @param smtpResponse Full SMTP response
   * @param maxRetries Maximum number of retries
   * @param currentRetry Current retry count
   * @param retryDelay Delay between retries in ms
   * @param context Additional context
   */
  public static temporary(
    message: string,
    recipientAddress: string,
    statusCode?: string,
    smtpResponse?: string,
    maxRetries: number = 3,
    currentRetry: number = 0,
    retryDelay: number = 60000,
    context: IErrorContext = {}
  ): MtaDeliveryError {
    const statusCodeStr = statusCode ? ` (${statusCode})` : '';
    const error = new MtaDeliveryError(
      `Temporary delivery failure to ${recipientAddress}${statusCodeStr}: ${message}`,
      {
        ...context,
        data: {
          ...context.data,
          recipientAddress,
          statusCode,
          smtpResponse,
          permanent: false
        },
        userMessage: `The email delivery to ${recipientAddress} failed temporarily. It will be retried.`
      }
    );
    
    return error.withRetry(maxRetries, currentRetry, retryDelay) as MtaDeliveryError;
  }

  /**
   * Check if this is a permanent delivery failure
   */
  public isPermanent(): boolean {
    return !!this.context.data?.permanent;
  }

  /**
   * Get the recipient address associated with this delivery error
   */
  public getRecipientAddress(): string | undefined {
    return this.context.data?.recipientAddress;
  }

  /**
   * Get the SMTP status code associated with this delivery error
   */
  public getStatusCode(): string | undefined {
    return this.context.data?.statusCode;
  }
}

/**
 * Error class for MTA configuration errors
 */
export class MtaConfigurationError extends ConfigurationError {
  /**
   * Creates a new MTA configuration error
   * 
   * @param message Error message
   * @param context Additional context
   */
  constructor(
    message: string,
    context: IErrorContext = {}
  ) {
    super(message, MTA_CONFIGURATION_ERROR, context);
  }

  /**
   * Creates an instance for a missing configuration value
   * 
   * @param propertyPath Path to the missing property
   * @param context Additional context
   */
  public static missingConfig(
    propertyPath: string,
    context: IErrorContext = {}
  ): MtaConfigurationError {
    return new MtaConfigurationError(
      `Missing required configuration: ${propertyPath}`,
      {
        ...context,
        data: {
          ...context.data,
          propertyPath
        },
        userMessage: `The mail server is missing required configuration.`
      }
    );
  }

  /**
   * Creates an instance for an invalid configuration value
   * 
   * @param propertyPath Path to the invalid property
   * @param value Current value
   * @param expectedType Expected type or format
   * @param context Additional context
   */
  public static invalidConfig(
    propertyPath: string,
    value: any,
    expectedType: string,
    context: IErrorContext = {}
  ): MtaConfigurationError {
    return new MtaConfigurationError(
      `Invalid configuration value for ${propertyPath}: got ${value} (${typeof value}), expected ${expectedType}`,
      {
        ...context,
        data: {
          ...context.data,
          propertyPath,
          value,
          expectedType
        },
        userMessage: `The mail server has an invalid configuration value.`
      }
    );
  }
}

/**
 * Error class for MTA DNS errors
 */
export class MtaDnsError extends NetworkError {
  /**
   * Creates a new MTA DNS error
   * 
   * @param message Error message
   * @param context Additional context
   */
  constructor(
    message: string,
    context: IErrorContext = {}
  ) {
    super(message, MTA_DNS_ERROR, context);
  }

  /**
   * Creates an instance for an MX record lookup failure
   * 
   * @param domain Domain that failed MX lookup
   * @param originalError Original error
   * @param context Additional context
   */
  public static mxLookupFailed(
    domain: string,
    originalError?: Error,
    context: IErrorContext = {}
  ): MtaDnsError {
    const errorMsg = originalError ? `: ${originalError.message}` : '';
    return new MtaDnsError(
      `Failed to lookup MX records for ${domain}${errorMsg}`,
      {
        ...context,
        data: {
          ...context.data,
          domain,
          recordType: 'MX',
          originalError: originalError ? {
            message: originalError.message,
            stack: originalError.stack
          } : undefined
        },
        userMessage: `Could not find mail servers for ${domain}.`
      }
    );
  }

  /**
   * Creates an instance for a TXT record lookup failure
   * 
   * @param domain Domain that failed TXT lookup
   * @param recordPrefix Optional record prefix (e.g., 'spf', 'dkim', 'dmarc')
   * @param originalError Original error
   * @param context Additional context
   */
  public static txtLookupFailed(
    domain: string,
    recordPrefix?: string,
    originalError?: Error,
    context: IErrorContext = {}
  ): MtaDnsError {
    const recordType = recordPrefix ? `${recordPrefix} TXT` : 'TXT';
    const errorMsg = originalError ? `: ${originalError.message}` : '';
    
    return new MtaDnsError(
      `Failed to lookup ${recordType} records for ${domain}${errorMsg}`,
      {
        ...context,
        data: {
          ...context.data,
          domain,
          recordType,
          recordPrefix,
          originalError: originalError ? {
            message: originalError.message,
            stack: originalError.stack
          } : undefined
        },
        userMessage: `Could not verify ${recordPrefix || ''} records for ${domain}.`
      }
    );
  }
}

/**
 * Error class for MTA timeout errors
 */
export class MtaTimeoutError extends NetworkError {
  /**
   * Creates a new MTA timeout error
   * 
   * @param message Error message
   * @param context Additional context
   */
  constructor(
    message: string,
    context: IErrorContext = {}
  ) {
    super(message, MTA_TIMEOUT_ERROR, context);
  }

  /**
   * Creates an instance for an SMTP command timeout
   * 
   * @param command SMTP command that timed out
   * @param server Server hostname
   * @param timeout Timeout in milliseconds
   * @param context Additional context
   */
  public static commandTimeout(
    command: string,
    server: string,
    timeout: number,
    context: IErrorContext = {}
  ): MtaTimeoutError {
    return new MtaTimeoutError(
      `SMTP command ${command} to ${server} timed out after ${timeout}ms`,
      {
        ...context,
        data: {
          ...context.data,
          command,
          server,
          timeout
        },
        userMessage: `The mail server took too long to respond.`
      }
    );
  }

  /**
   * Creates an instance for an overall transaction timeout
   * 
   * @param server Server hostname
   * @param timeout Timeout in milliseconds
   * @param context Additional context
   */
  public static transactionTimeout(
    server: string,
    timeout: number,
    context: IErrorContext = {}
  ): MtaTimeoutError {
    return new MtaTimeoutError(
      `SMTP transaction with ${server} timed out after ${timeout}ms`,
      {
        ...context,
        data: {
          ...context.data,
          server,
          timeout
        },
        userMessage: `The mail server transaction took too long to complete.`
      }
    );
  }
}

/**
 * Error class for MTA protocol errors
 */
export class MtaProtocolError extends OperationError {
  /**
   * Creates a new MTA protocol error
   * 
   * @param message Error message
   * @param context Additional context
   */
  constructor(
    message: string,
    context: IErrorContext = {}
  ) {
    super(message, MTA_PROTOCOL_ERROR, context);
  }

  /**
   * Creates an instance for an unexpected server response
   * 
   * @param command SMTP command that received unexpected response
   * @param response Unexpected response
   * @param expected Expected response pattern
   * @param server Server hostname
   * @param context Additional context
   */
  public static unexpectedResponse(
    command: string,
    response: string,
    expected: string,
    server: string,
    context: IErrorContext = {}
  ): MtaProtocolError {
    return new MtaProtocolError(
      `Unexpected SMTP response from ${server} for command ${command}: got "${response}", expected "${expected}"`,
      {
        ...context,
        data: {
          ...context.data,
          command,
          response,
          expected,
          server
        },
        userMessage: `Received an unexpected response from the mail server.`
      }
    );
  }

  /**
   * Creates an instance for a syntax error
   * 
   * @param details Error details
   * @param server Server hostname
   * @param context Additional context
   */
  public static syntaxError(
    details: string,
    server: string,
    context: IErrorContext = {}
  ): MtaProtocolError {
    return new MtaProtocolError(
      `SMTP syntax error in communication with ${server}: ${details}`,
      {
        ...context,
        data: {
          ...context.data,
          details,
          server
        },
        userMessage: `There was a protocol error communicating with the mail server.`
      }
    );
  }
}