import { Injectable, Logger, OnModuleInit, OnModuleDestroy, Inject } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ClientKafka } from '@nestjs/microservices';
import { OutboxRepository, OutboxEvent } from '../persistence/repositories/outbox.repository';

/**
 * Outbox Publisher Service (B方案 - 消费方确认模式)
 */
@Injectable()
export class OutboxPublisherService implements OnModuleInit, OnModuleDestroy {
  private readonly logger = new Logger(OutboxPublisherService.name);
  private isRunning = false;
  private pollInterval: NodeJS.Timeout | null = null;
  private timeoutCheckInterval: NodeJS.Timeout | null = null;
  private cleanupInterval: NodeJS.Timeout | null = null;
  private isConnected = false;

  private readonly pollIntervalMs: number;
  private readonly batchSize: number;
  private readonly cleanupIntervalMs: number;
  private readonly confirmationTimeoutMinutes: number;
  private readonly timeoutCheckIntervalMs: number;

  constructor(
    @Inject('KAFKA_SERVICE')
    private readonly kafkaClient: ClientKafka,
    private readonly outboxRepository: OutboxRepository,
    private readonly configService: ConfigService,
  ) {
    this.pollIntervalMs = Number(this.configService.get('OUTBOX_POLL_INTERVAL_MS', 1000));
    this.batchSize = Number(this.configService.get('OUTBOX_BATCH_SIZE', 100));
    this.cleanupIntervalMs = Number(this.configService.get('OUTBOX_CLEANUP_INTERVAL_MS', 3600000));
    this.confirmationTimeoutMinutes = Number(this.configService.get('OUTBOX_CONFIRMATION_TIMEOUT_MINUTES', 5));
    this.timeoutCheckIntervalMs = Number(this.configService.get('OUTBOX_TIMEOUT_CHECK_INTERVAL_MS', 60000));
  }

  async onModuleInit() {
    try {
      await this.kafkaClient.connect();
      this.isConnected = true;
      this.logger.log('[OUTBOX] Connected to Kafka');
      this.start();
    } catch (error) {
      this.logger.error('[OUTBOX] Failed to connect to Kafka:', error);
    }
  }

  async onModuleDestroy() {
    this.stop();
    if (this.isConnected) {
      await this.kafkaClient.close();
    }
  }

  start(): void {
    if (this.isRunning) return;
    this.isRunning = true;

    this.pollInterval = setInterval(() => {
      this.processOutbox().catch((err) => {
        this.logger.error('[OUTBOX] Error processing outbox:', err);
      });
    }, this.pollIntervalMs);

    this.timeoutCheckInterval = setInterval(() => {
      this.checkConfirmationTimeouts().catch((err) => {
        this.logger.error('[OUTBOX] Error checking timeouts:', err);
      });
    }, this.timeoutCheckIntervalMs);

    this.cleanupInterval = setInterval(() => {
      this.cleanup().catch((err) => {
        this.logger.error('[OUTBOX] Error cleaning up:', err);
      });
    }, this.cleanupIntervalMs);

    this.logger.log('[OUTBOX] Outbox publisher started (B方案)');
  }

  stop(): void {
    if (!this.isRunning) return;
    this.isRunning = false;

    if (this.pollInterval) { clearInterval(this.pollInterval); this.pollInterval = null; }
    if (this.timeoutCheckInterval) { clearInterval(this.timeoutCheckInterval); this.timeoutCheckInterval = null; }
    if (this.cleanupInterval) { clearInterval(this.cleanupInterval); this.cleanupInterval = null; }
  }

  async processOutbox(): Promise<void> {
    if (!this.isConnected) return;

    try {
      const pendingEvents = await this.outboxRepository.findPendingEvents(this.batchSize);
      const retryEvents = await this.outboxRepository.findEventsForRetry(Math.floor(this.batchSize / 2));
      const allEvents = [...pendingEvents, ...retryEvents];

      if (allEvents.length === 0) return;

      for (const event of allEvents) {
        await this.publishEvent(event);
      }
    } catch (error) {
      this.logger.error('[OUTBOX] Error in processOutbox:', error);
    }
  }

  private async publishEvent(event: OutboxEvent): Promise<void> {
    try {
      const payload = {
        ...(event.payload as Record<string, unknown>),
        _outbox: {
          id: event.id.toString(),
          aggregateId: event.aggregateId,
          eventType: event.eventType,
        },
      };

      this.kafkaClient.emit(event.topic, {
        key: event.key,
        value: JSON.stringify(payload),
      });

      await this.outboxRepository.markAsSent(event.id);
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      await this.outboxRepository.markAsFailed(event.id, errorMessage);
    }
  }

  private async checkConfirmationTimeouts(): Promise<void> {
    if (!this.isConnected) return;

    try {
      const timedOutEvents = await this.outboxRepository.findSentEventsTimedOut(
        this.confirmationTimeoutMinutes,
        this.batchSize,
      );

      for (const event of timedOutEvents) {
        await this.outboxRepository.resetSentToPending(event.id);
      }
    } catch (error) {
      this.logger.error('[OUTBOX] Error checking timeouts:', error);
    }
  }

  private async cleanup(): Promise<void> {
    const retentionDays = this.configService.get<number>('OUTBOX_RETENTION_DAYS', 7);
    await this.outboxRepository.cleanupOldEvents(retentionDays);
  }

  async getStats() {
    const stats = await this.outboxRepository.getStats();
    return { isRunning: this.isRunning, isConnected: this.isConnected, ...stats };
  }
}
