import { SESClient, SendEmailCommand, SendEmailCommandInput } from '@aws-sdk/client-ses';
import { fromEnv } from '@aws-sdk/credential-providers';
import {
  EmailOptions,
  EmailResponse,
  EmailRecipient,
  SesConfig,
  EmailProviderType,
} from '../interfaces/email-options.interface';
import { ObservabilityService } from './observability.service';

/**
 * SES Email Service Implementation
 * Handles email sending through AWS SES with comprehensive error handling
 */
export class SesService {
  private client: SESClient;
  private config: SesConfig;
  private observability: ObservabilityService;

  constructor(config: SesConfig) {
    this.config = config;
    this.client = this.createSesClient();
    this.observability = ObservabilityService.getInstance();
  }

  /**
   * Create SES client with proper configuration
   */
  private createSesClient(): SESClient {
    const clientConfig: any = {
      region: this.config.region,
      apiVersion: this.config.apiVersion || '2010-12-01',
      maxAttempts: this.config.maxRetries || 3,
    };

    // Configure credentials
    if (this.config.credentials) {
      clientConfig.credentials = this.config.credentials;
    } else if (this.config.accessKeyId && this.config.secretAccessKey) {
      clientConfig.credentials = {
        accessKeyId: this.config.accessKeyId,
        secretAccessKey: this.config.secretAccessKey,
        sessionToken: this.config.sessionToken,
      };
    } else {
      // Use AWS credential providers chain
      clientConfig.credentials = fromEnv();
    }

    // Configure endpoint if provided
    if (this.config.endpoint) {
      clientConfig.endpoint = this.config.endpoint;
    }

    // Configure HTTP options
    if (this.config.httpOptions) {
      clientConfig.requestHandler = {
        httpOptions: this.config.httpOptions,
      };
    }

    return new SESClient(clientConfig);
  }

  /**
   * Send email using SES
   */
  async sendEmail(options: EmailOptions): Promise<EmailResponse> {
    const startTime = Date.now();
    const eventId = this.observability.trackEmailAttempt(options, EmailProviderType.SES, startTime);

    try {
      const command = this.buildSendEmailCommand(options);
      const result = await this.client.send(command);

      const duration = Date.now() - startTime;
      const response: EmailResponse = {
        success: true,
        messageId: result.MessageId,
      };

      this.observability.trackEmailSuccess(eventId, response, duration, EmailProviderType.SES);

      return response;
    } catch (error: any) {
      const duration = Date.now() - startTime;
      const response = this.handleSesError(error, 'send email');
      
      this.observability.trackEmailFailure(eventId, error, duration, EmailProviderType.SES);
      
      // Track SES-specific error if available
      if (error.name) {
        this.observability.trackSesError(error, error.name as any, duration);
      }

      return response;
    }
  }

  /**
   * Build SES SendEmailCommand from EmailOptions
   */
  private buildSendEmailCommand(options: EmailOptions): SendEmailCommand {
    const input: SendEmailCommandInput = {
      Source: this.formatEmailAddress(options.from),
      Destination: {
        ToAddresses: this.formatRecipients(options.to),
        CcAddresses: options.cc ? this.formatRecipients(options.cc) : undefined,
        BccAddresses: options.bcc ? this.formatRecipients(options.bcc) : undefined,
      },
      Message: {
        Subject: {
          Data: options.subject,
          Charset: 'UTF-8',
        },
        Body: this.buildMessageBody(options),
      },
    };

    // Add reply-to if specified
    if (options.replyTo) {
      input.ReplyToAddresses = [options.replyTo];
    }

    // Add configuration set if specified in headers
    if (options.headers?.configurationSetName) {
      input.ConfigurationSetName = options.headers.configurationSetName;
    }

    // Add tags if specified in headers
    if (options.headers?.tags) {
      try {
        const tags = JSON.parse(options.headers.tags);
        if (Array.isArray(tags)) {
          input.Tags = tags.map(tag => ({
            Name: tag.name || tag.Name,
            Value: tag.value || tag.Value,
          }));
        }
      } catch (error) {
        // Ignore invalid tags format
      }
    }

    return new SendEmailCommand(input);
  }

  /**
   * Build message body for SES
   */
  private buildMessageBody(options: EmailOptions): any {
    const body: any = {};

    if (options.text) {
      body.Text = {
        Data: options.text,
        Charset: 'UTF-8',
      };
    }

    if (options.html) {
      body.Html = {
        Data: options.html,
        Charset: 'UTF-8',
      };
    }

    // SES doesn't support attachments in the same way as SMTP/SendGrid
    // Attachments would need to be handled differently (e.g., as links or embedded content)
    if (options.attachments && options.attachments.length > 0) {
      console.warn('SES attachments are not supported in this implementation. Consider using S3 links or embedding content.');
    }

    return body;
  }

  /**
   * Format email address for SES
   */
  private formatEmailAddress(recipient: EmailRecipient): string {
    if (typeof recipient === 'string') {
      return recipient;
    }
    return recipient.name ? `${recipient.name} <${recipient.email}>` : recipient.email;
  }

  /**
   * Format recipients for SES
   */
  private formatRecipients(recipients: EmailRecipient | EmailRecipient[]): string[] {
    if (Array.isArray(recipients)) {
      return recipients.map(recipient => this.formatEmailAddress(recipient));
    }
    return [this.formatEmailAddress(recipients)];
  }

  /**
   * Handle SES-specific errors
   */
  private handleSesError(error: any, operation: string): EmailResponse {
    console.error(`SES ${operation} failed:`, error);

    let errorMessage = `SES ${operation} failed`;
    let errorCode = 'SES_ERROR';
    let provider = EmailProviderType.SES;

    // Handle specific AWS SES errors
    if (error.name) {
      switch (error.name) {
        case 'MessageRejected':
          errorMessage = 'Email message was rejected by SES';
          errorCode = 'MESSAGE_REJECTED';
          break;
        case 'MailFromDomainNotVerified':
          errorMessage = 'Sender domain is not verified in SES';
          errorCode = 'DOMAIN_NOT_VERIFIED';
          break;
        case 'ConfigurationSetDoesNotExist':
          errorMessage = 'SES configuration set does not exist';
          errorCode = 'CONFIGURATION_SET_NOT_FOUND';
          break;
        case 'TemplateDoesNotExist':
          errorMessage = 'SES template does not exist';
          errorCode = 'TEMPLATE_NOT_FOUND';
          break;
        case 'AccountSendingPaused':
          errorMessage = 'SES account sending is paused';
          errorCode = 'ACCOUNT_PAUSED';
          break;
        case 'SendingPaused':
          errorMessage = 'SES sending is paused';
          errorCode = 'SENDING_PAUSED';
          break;
        case 'MessageTooLarge':
          errorMessage = 'Email message is too large for SES';
          errorCode = 'MESSAGE_TOO_LARGE';
          break;
        case 'InvalidParameterValue':
          errorMessage = 'Invalid parameter value provided to SES';
          errorCode = 'INVALID_PARAMETER';
          break;
        case 'InvalidParameter':
          errorMessage = 'Invalid parameter provided to SES';
          errorCode = 'INVALID_PARAMETER';
          break;
        case 'ValidationError':
          errorMessage = 'SES validation error';
          errorCode = 'VALIDATION_ERROR';
          break;
        case 'ThrottlingException':
          errorMessage = 'SES rate limit exceeded';
          errorCode = 'RATE_LIMIT_EXCEEDED';
          break;
        case 'ServiceUnavailable':
          errorMessage = 'SES service is temporarily unavailable';
          errorCode = 'SERVICE_UNAVAILABLE';
          break;
        case 'InternalFailure':
          errorMessage = 'SES internal error';
          errorCode = 'INTERNAL_ERROR';
          break;
        case 'NetworkError':
          errorMessage = 'Network error connecting to SES';
          errorCode = 'NETWORK_ERROR';
          break;
        case 'TimeoutError':
          errorMessage = 'SES request timed out';
          errorCode = 'TIMEOUT_ERROR';
          break;
        default:
          errorMessage = error.message || `SES ${operation} failed`;
          errorCode = error.name || 'SES_ERROR';
      }
    }

    return {
      success: false,
      error: {
        message: errorMessage,
        code: errorCode,
        provider,
        details: {
          sesResponse: {
            name: error.name,
            message: error.message,
            code: error.$metadata?.httpStatusCode,
            requestId: error.$metadata?.requestId,
            cfId: error.$metadata?.cfId,
            extendedRequestId: error.$metadata?.extendedRequestId,
          },
        },
      },
    };
  }

  /**
   * Verify SES connection
   */
  async verifyConnection(): Promise<boolean> {
    const startTime = Date.now();
    
    try {
      // SES doesn't have a direct "verify" method like SMTP
      // We'll try to get the sending quota as a connection test
      const { GetSendQuotaCommand } = await import('@aws-sdk/client-ses');
      const command = new GetSendQuotaCommand({});
      await this.client.send(command);
      
      const duration = Date.now() - startTime;
      this.observability.trackConnectionVerification(EmailProviderType.SES, true, duration);
      
      return true;
    } catch (error: any) {
      const duration = Date.now() - startTime;
      this.observability.trackConnectionVerification(EmailProviderType.SES, false, duration);
      
      console.error('SES connection verification failed:', error);
      return false;
    }
  }

  /**
   * Get SES sending statistics
   */
  async getSendingStatistics(): Promise<any> {
    try {
      const { GetSendStatisticsCommand } = await import('@aws-sdk/client-ses');
      const command = new GetSendStatisticsCommand({});
      const result = await this.client.send(command);
      
      // Track statistics in observability
      if (result.SendDataPoints && result.SendDataPoints.length > 0) {
        const latestStats = result.SendDataPoints[0];
        this.observability.trackSesStatistics({
          deliveryAttempts: latestStats.DeliveryAttempts || 0,
          bounces: latestStats.Bounces || 0,
          complaints: latestStats.Complaints || 0,
          rejects: latestStats.Rejects || 0,
        });
      }
      
      return result.SendDataPoints;
    } catch (error: any) {
      console.error('Failed to get SES sending statistics:', error);
      return null;
    }
  }

  /**
   * Get SES sending quota
   */
  async getSendingQuota(): Promise<any> {
    try {
      const { GetSendQuotaCommand } = await import('@aws-sdk/client-ses');
      const command = new GetSendQuotaCommand({});
      const result = await this.client.send(command);
      
      const quotaData = {
        max24HourSend: result.Max24HourSend || 0,
        sentLast24Hours: result.SentLast24Hours || 0,
        maxSendRate: result.MaxSendRate || 0,
      };
      
      // Track quota usage in observability
      this.observability.trackSesQuotaUsage(quotaData);
      
      return quotaData;
    } catch (error: any) {
      console.error('Failed to get SES sending quota:', error);
      return null;
    }
  }

  /**
   * Destroy SES client
   */
  destroy(): void {
    if (this.client) {
      this.client.destroy();
    }
  }
}
