/**
 * Email Processor
 * 
 * Processes email messages from the SQS queue by calling the CommunicationService.
 * This processor:
 * 1. Receives email messages from the queue
 * 2. Transforms them to CommunicationService format
 * 3. Calls the existing email sending logic
 * 4. Returns success/failure result
 */

import { Injectable } from '@nestjs/common';
import { QueueProcessor } from '../services/processor-registry.service';
import {
  QueueMessage,
  EmailQueueMessage,
  QueueProcessingResult,
} from '../interfaces/queue-message.interface';
import { QUEUE_CONSTANTS } from '../constants/queue.constants';
import { CommunicationService } from 'src/communication/communication.service';
import { SendSingleEmailDto } from 'src/communication/dto/email-communication.dto';
import { AwsS3Service } from 'src/common/services/awsS3.service';
import { classifyQueueError } from '../utils/queue-error.util';
import { maskEmailAddress } from '../utils/pii-mask.util';
import { AppLoggerService } from 'src/common/services/logger.service';

@Injectable()
export class EmailProcessor implements QueueProcessor {
  constructor(
    private readonly communicationService: CommunicationService,
    private readonly awsS3Service: AwsS3Service,
    private readonly logger: AppLoggerService,
  ) {}

  /**
   * Process an email message from the queue
   */
  async process(message: QueueMessage): Promise<QueueProcessingResult> {
    const startTime = Date.now();

    // Type guard to ensure this is an email message
    if (
      message.queueType !== QUEUE_CONSTANTS.QUEUE_TYPES.COMMUNICATION ||
      message.subType !== QUEUE_CONSTANTS.COMMUNICATION_TYPES.EMAIL
    ) {
      return {
        success: false,
        messageId: message.messageId || 'unknown',
        queueType: message.queueType,
        processingTimeMs: Date.now() - startTime,
        error: {
          code: 'INVALID_MESSAGE_TYPE',
          message: `Expected email message, got: ${message.queueType}/${message.subType}`,
          retryable: false,
        },
      };
    }

    const emailMessage = message as EmailQueueMessage;

    try {
      this.logger.debug(
        `Processing email message: ${maskEmailAddress(emailMessage.data.to.emailAddress)} ` +
        `(Attempt: ${emailMessage.attemptCount})`
      );

      // Resolve attachments: download from S3 just before sending if needed
      const resolvedAttachments =
        emailMessage.data.attachments && emailMessage.data.attachments.length > 0
          ? await Promise.all(
              emailMessage.data.attachments.map(async (att) => {
                try {
                  // If content is already provided (small attachment), use it directly
                  if (att.content) {
                    return {
                      content: att.content,
                      mime_type: att.contentType,
                      name: att.name,
                    };
                  }

                  // If s3Key is provided, download from S3 and convert to base64
                  if (att.s3Key) {
                    const bucketInfo =
                      att.s3Bucket && att.s3Bucket.trim().length > 0
                        ? `bucket=${att.s3Bucket}`
                        : 'default bucket';

                    this.logger.log(
                      `Downloading attachment from S3: key=${att.s3Key}, ${bucketInfo}`,
                    );

                    // ✅ Retry logic with exponential backoff
                    let buffer: Buffer | null = null;
                    let retryCount = 0;
                    const maxRetries = 3;

                    while (retryCount < maxRetries && !buffer) {
                      try {
                        // S3 bucket is configured in AwsS3Service constructor from environment
                        buffer = await this.awsS3Service.getS3ObjectAsBuffer(att.s3Key);

                        if (!buffer) {
                          throw new Error('Empty buffer returned from S3');
                        }
                      } catch (s3Error) {
                        retryCount++;
                        if (retryCount < maxRetries) {
                          const delay = 1000 * Math.pow(2, retryCount); // 2s, 4s, 8s
                          this.logger.warn(
                            `S3 download failed (attempt ${retryCount}/${maxRetries}), ` +
                            `retrying in ${delay}ms: key=${att.s3Key}`,
                          );
                          await new Promise(resolve => setTimeout(resolve, delay));
                        } else {
                          this.logger.error(
                            `Failed to download S3 attachment after ${maxRetries} attempts: ` +
                            `key=${att.s3Key}. Skipping attachment.`,
                            '',
                            {
                              error: s3Error instanceof Error ? s3Error.message : String(s3Error),
                              stack: s3Error instanceof Error ? s3Error.stack : undefined,
                              s3Key: att.s3Key,
                              name: att.name,
                              attemptCount: emailMessage.attemptCount,
                            },
                          );
                          return null;
                        }
                      }
                    }

                    if (!buffer) {
                      return null;
                    }

                    const base64Content = buffer.toString('base64');
                    const sizeKB = Math.round(buffer.length / 1024);

                    this.logger.log(
                      `Successfully downloaded attachment: ${att.name} (${sizeKB}KB)`,
                    );

                    return {
                      content: base64Content,
                      mime_type: att.contentType,
                      name: att.name,
                    };
                  }

                  // No usable content; log and skip
                  this.logger.warn(
                    `Attachment for email ${emailMessage.data.to.emailAddress} is missing both content and s3Key. Skipping.`,
                  );
                  return null;
                } catch (attachmentError) {
                  this.logger.error(
                    `Error processing attachment: ${att.name}`,
                    '',
                    {
                      error:
                        attachmentError instanceof Error
                          ? attachmentError.message
                          : String(attachmentError),
                      stack: attachmentError instanceof Error ? attachmentError.stack : undefined,
                      name: att.name,
                      s3Key: att.s3Key,
                      attemptCount: emailMessage.attemptCount,
                    },
                  );
                  return null; // Skip failed attachment but continue with email
                }
              }),
            )
          : undefined;

      // Filter out null attachments
      const validAttachments = resolvedAttachments?.filter((att) => att !== null);

      // Transform queue message to CommunicationService DTO
      const emailDto: SendSingleEmailDto = {
        to: {
          emailAddress: emailMessage.data.to.emailAddress,
          name: emailMessage.data.to.name,
        },
        from: {
          address: emailMessage.data.from.address,
          name: emailMessage.data.from.name,
        },
        subject: emailMessage.data.subject,
        htmlbody: emailMessage.data.htmlBody,
        textbody: emailMessage.data.textBody,
        templateKey: emailMessage.data.templateKey,
        mergeInfo: emailMessage.data.templateData,
        cc: emailMessage.data.cc?.map(cc => ({
          emailAddress: cc.emailAddress,
          name: cc.name,
        })),
        bcc: emailMessage.data.bcc?.map(bcc => ({
          emailAddress: bcc.emailAddress,
          name: bcc.name,
        })),
        attachments: validAttachments as any, // Already filtered for null values
      } as any; // Use 'as any' to bypass trackinfo type checking

      // Add trackinfo if available
      if (emailMessage.metadata?.registrationId) {
        emailDto.trackinfo = {
          registrationId: emailMessage.metadata.registrationId,
          createdBy: emailMessage.metadata.createdBy,
          updatedBy: emailMessage.metadata.updatedBy,
          templateId: emailMessage.metadata.templateId,
        };
      }

      // Call the existing CommunicationService
      await this.communicationService.sendSingleEmail(emailDto);

      const processingTime = Date.now() - startTime;

      this.logger.log(
        `Successfully sent email to: ${maskEmailAddress(emailMessage.data.to.emailAddress)} ` +
        `in ${processingTime}ms`
      );

      return {
        success: true,
        messageId: emailMessage.messageId || 'unknown',
        queueType: emailMessage.queueType,
        processingTimeMs: processingTime,
      };
    } catch (error) {
      const classified = classifyQueueError(error);

      this.logger.error(
        `Failed to send email to: ${maskEmailAddress(emailMessage.data.to.emailAddress)}`,
        '',
        {
          error: classified.message,
          stack: classified.stack,
          to: emailMessage.data.to.emailAddress,
          templateKey: emailMessage.data.templateKey,
          attemptCount: emailMessage.attemptCount,
        },
      );

      return {
        success: false,
        messageId: emailMessage.messageId || 'unknown',
        queueType: emailMessage.queueType,
        processingTimeMs: Date.now() - startTime,
        error: {
          code: classified.code,
          message: classified.message,
          stack: classified.stack,
          retryable: classified.retryable,
        },
      };
    }
  }
}
