/**
 * Queue attachment resolution
 *
 * A queued email carries each attachment either as inline base64 `content` (small files) or as an
 * `s3Key` reference (large files — a base64 multi-MB payload would blow past SQS's 256 KB message
 * limit). References are resolved to real bytes HERE, in the consumer, immediately before the
 * provider call.
 *
 * Shared by EmailProcessor and BulkEmailProcessor so the two never drift on retry behaviour or on
 * what happens to an attachment that can't be fetched.
 */

import { AwsS3Service } from 'src/common/services/awsS3.service';
import { AppLoggerService } from 'src/common/services/logger.service';
import { EmailAttachments } from 'src/communication/dto/email-communication.dto';
import { QUEUE_CONSTANTS } from '../constants/queue.constants';

/** An attachment as it travels on a queue message: inline bytes, or a reference to fetch. */
export interface QueuedAttachment {
  name: string;
  contentType: string;
  /** Base64 bytes, for attachments small enough to ride inline. */
  content?: string;
  /** S3 object key, resolved to bytes by {@link resolveQueuedAttachments}. */
  s3Key?: string;
  /** Optional bucket override; the default application bucket is used when absent. */
  s3Bucket?: string;
}

/**
 * Downloads one S3-referenced attachment, retrying with exponential backoff (2s, 4s, …) up to
 * QUEUE_CONSTANTS.MAX_RECEIVE_COUNT attempts. Returns null when every attempt fails.
 */
async function downloadWithRetries(
  attachment: QueuedAttachment,
  awsS3Service: AwsS3Service,
  logger: AppLoggerService,
): Promise<Buffer | null> {
  const bucketInfo =
    attachment.s3Bucket && attachment.s3Bucket.trim().length > 0
      ? `bucket=${attachment.s3Bucket}`
      : 'default bucket';
  logger.log(`Downloading attachment from S3: key=${attachment.s3Key}, ${bucketInfo}`);

  const maxRetries = QUEUE_CONSTANTS.MAX_RECEIVE_COUNT;
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const buffer = await awsS3Service.getS3ObjectAsBuffer(attachment.s3Key as string);
      if (!buffer) {
        throw new Error('Empty buffer returned from S3');
      }
      return buffer;
    } catch (error) {
      if (attempt < maxRetries) {
        const delayMs = 1000 * Math.pow(2, attempt);
        logger.warn(
          `S3 download failed (attempt ${attempt}/${maxRetries}), ` +
            `retrying in ${delayMs}ms: key=${attachment.s3Key}`,
        );
        await new Promise((resolve) => setTimeout(resolve, delayMs));
        continue;
      }
      logger.error(
        `Failed to download S3 attachment after ${maxRetries} attempts: ` +
          `key=${attachment.s3Key}. Skipping attachment.`,
        '',
        {
          error: error instanceof Error ? error.message : String(error),
          stack: error instanceof Error ? error.stack : undefined,
          s3Key: attachment.s3Key,
          name: attachment.name,
        },
      );
      return null;
    }
  }
  return null;
}

/**
 * Resolves every queued attachment to provider-ready base64 form.
 *
 * Inline `content` is passed through untouched; an `s3Key` is downloaded (see
 * {@link downloadWithRetries}). An attachment that can neither be read nor fetched is DROPPED with
 * an error logged, rather than failing the whole message — one unreachable file must not stop an
 * email (or an entire bulk batch) from going out. Returns undefined when there is nothing to
 * attach, matching what the provider DTOs expect.
 */
export async function resolveQueuedAttachments(
  attachments: QueuedAttachment[] | undefined,
  awsS3Service: AwsS3Service,
  logger: AppLoggerService,
  context?: string,
): Promise<EmailAttachments[] | undefined> {
  if (!attachments?.length) {
    return undefined;
  }

  const resolved = await Promise.all(
    attachments.map(async (attachment): Promise<EmailAttachments | null> => {
      try {
        if (attachment.content) {
          return {
            content: attachment.content,
            mime_type: attachment.contentType,
            name: attachment.name,
          };
        }

        if (attachment.s3Key) {
          const buffer = await downloadWithRetries(attachment, awsS3Service, logger);
          if (!buffer) {
            return null;
          }
          logger.log(
            `Successfully downloaded attachment: ${attachment.name} ` +
              `(${Math.round(buffer.length / 1024)}KB)`,
          );
          return {
            content: buffer.toString('base64'),
            mime_type: attachment.contentType,
            name: attachment.name,
          };
        }

        logger.warn(
          `Attachment${context ? ` for ${context}` : ''} is missing both content and s3Key. ` +
            `Skipping: ${attachment.name}`,
        );
        return null;
      } catch (error) {
        logger.error(`Error processing attachment: ${attachment.name}`, '', {
          error: error instanceof Error ? error.message : String(error),
          stack: error instanceof Error ? error.stack : undefined,
          name: attachment.name,
          s3Key: attachment.s3Key,
        });
        return null;
      }
    }),
  );

  const usable = resolved.filter((attachment): attachment is EmailAttachments => attachment !== null);
  return usable.length ? usable : undefined;
}
