/**
 * SQS Poller Service
 * 
 * Polls SQS queues at regular intervals using NestJS Schedule (@Cron decorator).
 * This is the core component that:
 * 1. Polls each configured queue using long polling
 * 2. Dispatches messages to appropriate processors via ProcessorRegistry
 * 3. Deletes successfully processed messages
 * 4. Handles errors and retries
 * 
 * IMPORTANT: This runs in your existing NestJS process - no separate worker needed!
 */

import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { SqsClientService } from './sqs-client.service';
import { ProcessorRegistryService } from './processor-registry.service';
import { getQueueUrls, getQueueFeatureFlags } from '../config/sqs.config';
import { QUEUE_CONSTANTS, QueueType } from '../constants/queue.constants';
import { Message } from '@aws-sdk/client-sqs';
import { AppLoggerService } from 'src/common/services/logger.service';

interface QueueConfig {
  queueType: QueueType;
  queueUrl: string;
  enabled: boolean;
}

@Injectable()
export class SqsPollerService implements OnModuleInit, OnModuleDestroy {
  private readonly queueConfigs: QueueConfig[] = [];
  private isPolling = false;
  private pollingStats = new Map<QueueType, {
    totalPolled: number;
    totalProcessed: number;
    totalFailed: number;
    lastPolledAt?: Date;
  }>();

  constructor(
    private readonly sqsClient: SqsClientService,
    private readonly processorRegistry: ProcessorRegistryService,
    private readonly logger: AppLoggerService,
  ) {
    this.initializeQueueConfigs();
  }

  /**
   * Initialize queue configurations from environment
   * Simplified to 2 queues: communication and heavy-processing
   */
  private initializeQueueConfigs(): void {
    const queueUrls = getQueueUrls();
    const featureFlags = getQueueFeatureFlags();

    // Map queue types to their URLs and feature flags
    const configs: Array<{ type: QueueType; url: string | undefined; enabled: boolean }> = [
      {
        type: QUEUE_CONSTANTS.QUEUE_TYPES.COMMUNICATION,
        url: queueUrls.communication,
        enabled: featureFlags.communicationQueueEnabled,
      },
      {
        type: QUEUE_CONSTANTS.QUEUE_TYPES.HEAVY_PROCESSING,
        url: queueUrls.heavyProcessing,
        enabled: featureFlags.heavyProcessingQueueEnabled,
      },
    ];

    // Filter out queues that are not configured or not enabled
    for (const config of configs) {
      if (config.url && config.enabled) {
        this.queueConfigs.push({
          queueType: config.type,
          queueUrl: config.url,
          enabled: config.enabled,
        });
        this.pollingStats.set(config.type, {
          totalPolled: 0,
          totalProcessed: 0,
          totalFailed: 0,
        });
      } else if (!config.url) {
        this.logger.warn(
          `Queue URL for '${config.type}' is not configured. Skipping this queue.`
        );
      } else if (!config.enabled) {
        this.logger.log(
          `Queue '${config.type}' is disabled via feature flag. Skipping this queue.`
        );
      }
    }

    this.logger.log(
      `Initialized ${this.queueConfigs.length} queue(s): ${this.queueConfigs.map(q => q.queueType).join(', ')}`
    );
  }

  /**
   * Lifecycle hook - called when module is initialized
   */
  async onModuleInit() {
    this.logger.log('SQS Poller Service initialized');
    
    // Validate that processors are registered for enabled queues
    // Check by subType (email/whatsapp/sms/invoice) not queueType (communication/heavy-processing)
    for (const config of this.queueConfigs) {
      const expectedSubTypes = this.getExpectedSubTypesForQueue(config.queueType);
      const missingProcessors: string[] = [];
      
      for (const subType of expectedSubTypes) {
        if (!this.processorRegistry.hasProcessor(subType)) {
          missingProcessors.push(subType);
        }
      }
      
      if (missingProcessors.length > 0) {
        this.logger.warn(
          `Missing processor(s) for enabled queue '${config.queueType}': ${missingProcessors.join(', ')}. ` +
          `Messages of these types will fail to process until processors are registered.`
        );
      } else {
        this.logger.log(
          `All processors registered for queue '${config.queueType}': ${expectedSubTypes.join(', ')}`
        );
      }
    }
  }

  /**
   * Get expected subTypes for a given queue type
   * Maps queue types to their expected message subTypes
   */
  private getExpectedSubTypesForQueue(queueType: QueueType): string[] {
    if (queueType === QUEUE_CONSTANTS.QUEUE_TYPES.COMMUNICATION) {
      return [
        QUEUE_CONSTANTS.COMMUNICATION_TYPES.EMAIL,
        QUEUE_CONSTANTS.COMMUNICATION_TYPES.WHATSAPP,
        QUEUE_CONSTANTS.COMMUNICATION_TYPES.SMS,
      ];
    } else if (queueType === QUEUE_CONSTANTS.QUEUE_TYPES.HEAVY_PROCESSING) {
      return [
        QUEUE_CONSTANTS.HEAVY_PROCESSING_TYPES.INVOICE,
        // Add other heavy processing types as they're implemented
      ];
    }
    return [];
  }

  /**
   * Lifecycle hook - called when module is being destroyed
   */
  onModuleDestroy() {
    this.logger.log('SQS Poller Service shutting down');
    this.isPolling = false;
  }

  /**
   * Main polling job - runs every 10 seconds
   * Uses cron expression to run at precise intervals
   * 
   * Cron pattern: every 10 seconds
   * You can adjust this in queue.constants.ts
   */
  @Cron(CronExpression.EVERY_10_SECONDS)
  async pollQueues() {
    // Prevent overlapping executions if previous poll is still running
    if (this.isPolling) {
      this.logger.debug('Previous polling cycle still in progress, skipping this iteration');
      return;
    }

    if (this.queueConfigs.length === 0) {
      // Only log once on startup, not every 10 seconds
      return;
    }

    this.isPolling = true;

    try {
      // Poll all configured queues concurrently
      await Promise.all(
        this.queueConfigs.map(config => this.pollSingleQueue(config))
      );
    } catch (error) {
      this.logger.error(
        'Error during queue polling cycle',
        '',
        { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined },
      );
    } finally {
      this.isPolling = false;
    }
  }

  /**
   * Poll a single queue and process all received messages
   */
  private async pollSingleQueue(config: QueueConfig): Promise<void> {
    try {
      // Use different visibility timeouts based on queue type
      const visibilityTimeout = 
        config.queueType === QUEUE_CONSTANTS.QUEUE_TYPES.HEAVY_PROCESSING
          ? QUEUE_CONSTANTS.HEAVY_PROCESSING_VISIBILITY_TIMEOUT_SECONDS
          : QUEUE_CONSTANTS.COMMUNICATION_VISIBILITY_TIMEOUT_SECONDS;

      // Use long polling to wait for messages (reduces empty responses)
      const messages = await this.sqsClient.receiveMessages(
        config.queueUrl,
        QUEUE_CONSTANTS.MAX_MESSAGES_PER_POLL,
        QUEUE_CONSTANTS.LONG_POLL_WAIT_TIME_SECONDS,
        visibilityTimeout,
      );

      if (messages.length === 0) {
        // No messages - this is normal, no need to log
        return;
      }

      this.logger.log(
        `Received ${messages.length} message(s) from ${config.queueType} queue`
      );

      // Update stats
      const stats = this.pollingStats.get(config.queueType);
      if (stats) {
        stats.totalPolled += messages.length;
        stats.lastPolledAt = new Date();
      }

      // Process all messages concurrently
      await Promise.all(
        messages.map(message => this.processMessage(message, config))
      );
    } catch (error) {
      this.logger.error(
        `Error polling queue: ${config.queueType}`,
        '',
        { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, queueType: config.queueType },
      );
    }
  }

  /**
   * Process a single message
   */
  private async processMessage(message: Message, config: QueueConfig): Promise<void> {
    const stats = this.pollingStats.get(config.queueType);

    try {
      // Hard guard: without a receipt handle we cannot delete; avoid crashing.
      if (!message.ReceiptHandle) {
        this.logger.error(
          `Received message without ReceiptHandle for ${config.queueType}. MessageId: ${message.MessageId}. Skipping processing.`,
          '',
          { queueType: config.queueType, messageId: message.MessageId },
        );
        if (stats) stats.totalFailed++;
        return;
      }

      // Treat missing body as malformed/non-retryable and delete immediately.
      if (!message.Body) {
        this.logger.warn(
          `Received message with empty body for ${config.queueType}. MessageId: ${message.MessageId}. Deleting malformed message.`,
          { queueType: config.queueType, messageId: message.MessageId },
        );
        await this.sqsClient.deleteMessage(config.queueUrl, message.ReceiptHandle);
        if (stats) stats.totalFailed++;
        return;
      }

      // Parse the message body
      const queueMessage = this.sqsClient.parseMessageBody(message);

      if (!queueMessage) {
        this.logger.error(
          `Failed to parse message body for ${config.queueType}. ` +
          `MessageId: ${message.MessageId}. Deleting malformed message.`
          ,
          '',
          { queueType: config.queueType, messageId: message.MessageId }
        );
        await this.sqsClient.deleteMessage(config.queueUrl, message.ReceiptHandle);
        if (stats) stats.totalFailed++;
        return;
      }

      // Add message ID from SQS
      queueMessage.messageId = message.MessageId;

      // Process the message via registry
      const result = await this.processorRegistry.processMessage(queueMessage);

      if (result.success) {
        // Success! Delete the message from the queue
        await this.sqsClient.deleteMessage(config.queueUrl, message.ReceiptHandle);
        
        if (stats) stats.totalProcessed++;
        
        this.logger.log(
          `Successfully processed ${config.queueType} message ` +
          `(ID: ${message.MessageId}, Time: ${result.processingTimeMs}ms)`
        );
      } else {
        // Processing failed
        if (stats) stats.totalFailed++;

        if (result.error?.retryable) {
          // Message will become visible again after visibility timeout
          // SQS will automatically retry based on redrive policy
          this.logger.warn(
            `Retryable error processing ${config.queueType} message: ${result.error.message}. ` +
            `Attempt: ${queueMessage.attemptCount}/${QUEUE_CONSTANTS.MAX_RECEIVE_COUNT}. ` +
            `Message will be retried.`
          );
        } else {
          // Non-retryable error - delete the message to prevent infinite processing
          this.logger.error(
            `Non-retryable error processing ${config.queueType} message: ${result.error?.message}. ` +
            `Deleting message to prevent reprocessing.`
          );
          await this.sqsClient.deleteMessage(config.queueUrl, message.ReceiptHandle);
        }
      }
    } catch (error) {
      // Unexpected error during processing
      if (stats) stats.totalFailed++;
      
      this.logger.error(
        `Unexpected error processing message from ${config.queueType} queue`,
        '',
        { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, queueType: config.queueType },
      );
      
      // Don't delete - let SQS retry with exponential backoff
    }
  }

  /**
   * Get polling statistics for monitoring
   */
  getPollingStats() {
    const stats: Record<string, any> = {};
    
    this.pollingStats.forEach((value, key) => {
      stats[key] = {
        ...value,
        successRate: value.totalPolled > 0 
          ? ((value.totalProcessed / value.totalPolled) * 100).toFixed(2) + '%'
          : 'N/A',
      };
    });

    return stats;
  }

  /**
   * Manually trigger a poll for a specific queue (useful for testing)
   */
  async triggerPoll(queueType: QueueType): Promise<void> {
    const config = this.queueConfigs.find(c => c.queueType === queueType);
    
    if (!config) {
      throw new Error(`Queue type '${queueType}' is not configured or not enabled`);
    }

    await this.pollSingleQueue(config);
  }

  /**
   * Get configured queues
   */
  getConfiguredQueues(): QueueConfig[] {
    return [...this.queueConfigs];
  }
}
