/**
 * SQS Client Service
 * 
 * Core wrapper around AWS SQS SDK providing:
 * - Send messages to queues
 * - Receive messages from queues (with long polling)
 * - Delete messages after successful processing
 * - Error handling and logging
 */

import { Injectable } from '@nestjs/common';
import {
  SQSClient,
  SendMessageCommand,
  ReceiveMessageCommand,
  DeleteMessageCommand,
  GetQueueAttributesCommand,
  Message,
  SendMessageCommandInput,
  ReceiveMessageCommandInput,
  DeleteMessageCommandInput,
} from '@aws-sdk/client-sqs';
import { getSqsClientConfig } from '../config/sqs.config';
import { QueueMessage, SqsMessageWrapper } from '../interfaces/queue-message.interface';
import { QUEUE_CONSTANTS } from '../constants/queue.constants';
import { AppLoggerService } from 'src/common/services/logger.service';
import { handleKnownErrors } from 'src/common/utils/handle-error.util';
import { ERROR_CODES } from 'src/common/constants/error-string-constants';

@Injectable()
export class SqsClientService {
  private readonly sqsClient: SQSClient;

  constructor(private readonly logger: AppLoggerService) {
    try {
      const config = getSqsClientConfig();
      this.sqsClient = new SQSClient(config);
      this.logger.log(`SQS Client initialized for region: ${config.region}`);
    } catch (error) {
      this.logger.error('Failed to initialize SQS Client', '', {
        error: error instanceof Error ? error.message : String(error),
        stack: error instanceof Error ? error.stack : undefined,
      });
      handleKnownErrors(ERROR_CODES.INTERNAL_SERVER_ERROR, error);
    }
  }

  /**
   * Send a message to an SQS queue
   * 
   * @param queueUrl - The URL of the SQS queue
   * @param message - The message payload to send
   * @param delaySeconds - Optional delay before message becomes available (0-900 seconds)
   * @returns Message ID if successful
   */
  async sendMessage(
    queueUrl: string,
    message: QueueMessage,
    delaySeconds: number = 0,
  ): Promise<string> {
    try {
      // Add timestamp if not present
      if (!message.timestamp) {
        message.timestamp = new Date().toISOString();
      }

      const messageBody = JSON.stringify(message);

      // Check message size
      const messageSize = Buffer.byteLength(messageBody, 'utf8');
      if (messageSize > QUEUE_CONSTANTS.MAX_MESSAGE_SIZE_BYTES) {
        throw new Error(
          `Message size (${messageSize} bytes) exceeds SQS limit (${QUEUE_CONSTANTS.MAX_MESSAGE_SIZE_BYTES} bytes)`
        );
      }

      const params: SendMessageCommandInput = {
        QueueUrl: queueUrl,
        MessageBody: messageBody,
        DelaySeconds: delaySeconds,
        MessageAttributes: {
          QueueType: {
            DataType: 'String',
            StringValue: message.queueType,
          },
          CorrelationId: {
            DataType: 'String',
            StringValue: message.correlationId || '',
          },
        },
      };

      const command = new SendMessageCommand(params);
      const response = await this.sqsClient.send(command);

      this.logger.debug(
        `Message sent to queue: ${queueUrl}, MessageId: ${response.MessageId}, Type: ${message.queueType}`
      );

      return response.MessageId!;
    } catch (error) {
      this.logger.error(`Failed to send message to queue: ${queueUrl}`, '', {
        error: error instanceof Error ? error.message : String(error),
        stack: error instanceof Error ? error.stack : undefined,
        queueUrl,
        queueType: message.queueType,
        correlationId: message.correlationId,
      });
      handleKnownErrors(ERROR_CODES.INTERNAL_SERVER_ERROR, error);
    }
  }

  /**
   * Receive messages from an SQS queue using long polling
   * 
   * @param queueUrl - The URL of the SQS queue
   * @param maxMessages - Maximum number of messages to receive (1-10)
   * @param waitTimeSeconds - Long polling wait time (0-20 seconds)
   * @param visibilityTimeout - How long messages are invisible to other consumers
   * @returns Array of messages
   */
  async receiveMessages(
    queueUrl: string,
    maxMessages: number = QUEUE_CONSTANTS.MAX_MESSAGES_PER_POLL,
    waitTimeSeconds: number = QUEUE_CONSTANTS.LONG_POLL_WAIT_TIME_SECONDS,
    visibilityTimeout: number = QUEUE_CONSTANTS.COMMUNICATION_VISIBILITY_TIMEOUT_SECONDS,
  ): Promise<Message[]> {
    try {
      const params: ReceiveMessageCommandInput = {
        QueueUrl: queueUrl,
        MaxNumberOfMessages: Math.min(maxMessages, 10), // SQS max is 10
        WaitTimeSeconds: Math.min(waitTimeSeconds, 20), // SQS max is 20
        VisibilityTimeout: visibilityTimeout,
        MessageAttributeNames: ['All'],
        AttributeNames: ['All'],
      };

      const command = new ReceiveMessageCommand(params);
      const response = await this.sqsClient.send(command);

      const messages = response.Messages || [];
      
      if (messages.length > 0) {
        this.logger.debug(
          `Received ${messages.length} message(s) from queue: ${queueUrl}`
        );
      }

      return messages;
    } catch (error) {
      this.logger.error(`Failed to receive messages from queue: ${queueUrl}`, '', {
        error: error instanceof Error ? error.message : String(error),
        stack: error instanceof Error ? error.stack : undefined,
        queueUrl,
      });
      handleKnownErrors(ERROR_CODES.INTERNAL_SERVER_ERROR, error);
    }
  }

  /**
   * Delete a message from an SQS queue after successful processing
   * 
   * @param queueUrl - The URL of the SQS queue
   * @param receiptHandle - The receipt handle of the message to delete
   */
  async deleteMessage(queueUrl: string, receiptHandle: string): Promise<void> {
    try {
      const params: DeleteMessageCommandInput = {
        QueueUrl: queueUrl,
        ReceiptHandle: receiptHandle,
      };

      const command = new DeleteMessageCommand(params);
      await this.sqsClient.send(command);

      this.logger.debug(`Message deleted from queue: ${queueUrl}`);
    } catch (error) {
      this.logger.error(`Failed to delete message from queue: ${queueUrl}`, '', {
        error: error instanceof Error ? error.message : String(error),
        stack: error instanceof Error ? error.stack : undefined,
        queueUrl,
      });
      handleKnownErrors(ERROR_CODES.INTERNAL_SERVER_ERROR, error);
    }
  }

  /**
   * Parse message body from SQS message
   * 
   * @param message - SQS message
   * @returns Parsed queue message
   */
  parseMessageBody(message: Message): QueueMessage | null {
    try {
      if (!message.Body) {
        this.logger.warn('Message has no body');
        return null;
      }

      const parsed = JSON.parse(message.Body) as QueueMessage;
      
      // Use SQS's ApproximateReceiveCount for accurate attempt tracking
      // This is the actual number of times SQS has delivered this message
      const approximateReceiveCount = message.Attributes?.ApproximateReceiveCount;
      parsed.attemptCount = approximateReceiveCount 
        ? parseInt(approximateReceiveCount, 10) 
        : 1;
      
      return parsed;
    } catch (error) {
      this.logger.error('Failed to parse message body', '', {
        error: error instanceof Error ? error.message : String(error),
        stack: error instanceof Error ? error.stack : undefined,
      });
      return null;
    }
  }

  /**
   * Get queue attributes like approximate number of messages
   * 
   * @param queueUrl - The URL of the SQS queue
   * @returns Queue attributes
   */
  async getQueueAttributes(queueUrl: string): Promise<Record<string, string>> {
    try {
      const command = new GetQueueAttributesCommand({
        QueueUrl: queueUrl,
        AttributeNames: ['All'],
      });

      const response = await this.sqsClient.send(command);
      return response.Attributes || {};
    } catch (error) {
      this.logger.error(`Failed to get queue attributes for: ${queueUrl}`, '', {
        error: error instanceof Error ? error.message : String(error),
        stack: error instanceof Error ? error.stack : undefined,
        queueUrl,
      });
      handleKnownErrors(ERROR_CODES.INTERNAL_SERVER_ERROR, error);
    }
  }

  /**
   * Health check - verifies SQS client is operational
   */
  async healthCheck(queueUrl: string): Promise<boolean> {
    try {
      await this.getQueueAttributes(queueUrl);
      return true;
    } catch (error) {
      this.logger.error('SQS health check failed', '', {
        error: error instanceof Error ? error.message : String(error),
        stack: error instanceof Error ? error.stack : undefined,
        queueUrl,
      });
      return false;
    }
  }
}
