import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { OutboxRepository } from '../../infrastructure/persistence/repositories/outbox.repository';
import { KafkaProducerService } from '../../infrastructure/kafka/kafka-producer.service';
import { RedisService } from '../../infrastructure/redis/redis.service';

/**
 * Outbox 事件发布调度器
 * 定期将 Outbox 中的事件发布到 Kafka
 * 使用指数退避重试策略，最大延迟3小时
 */
@Injectable()
export class OutboxScheduler implements OnModuleInit {
  private readonly logger = new Logger(OutboxScheduler.name);
  private readonly LOCK_KEY = 'mining:outbox:scheduler:lock';

  constructor(
    private readonly outboxRepository: OutboxRepository,
    private readonly kafkaProducer: KafkaProducerService,
    private readonly redis: RedisService,
  ) {}

  onModuleInit() {
    this.logger.log('Outbox scheduler initialized');
  }

  /**
   * 每30秒发布 Outbox 中的待处理事件
   * 使用指数退避重试策略
   */
  @Cron('*/30 * * * * *')
  async publishOutboxEvents(): Promise<void> {
    const lockValue = await this.redis.acquireLock(
      `${this.LOCK_KEY}:publish`,
      25,
    );
    if (!lockValue) {
      return; // 其他实例正在处理
    }

    try {
      const events = await this.outboxRepository.findPendingEvents(100);

      if (events.length === 0) {
        return;
      }

      let successCount = 0;
      let failCount = 0;

      for (const event of events) {
        try {
          await this.kafkaProducer.emit(event.topic, {
            key: event.key || event.aggregateId,
            value: {
              eventId: event.id,
              aggregateType: event.aggregateType,
              aggregateId: event.aggregateId,
              eventType: event.eventType,
              payload: event.payload,
              createdAt: event.createdAt.toISOString(),
            },
          });

          // 标记为已发布
          await this.outboxRepository.markAsPublished(event.id);
          successCount++;
        } catch (error) {
          failCount++;
          const errorMessage =
            error instanceof Error ? error.message : 'Unknown error';
          this.logger.error(
            `Failed to publish event ${event.id} (retry ${event.retryCount}/${event.maxRetries})`,
            error,
          );

          // 标记失败并设置指数退避重试时间
          await this.outboxRepository.markAsFailed(
            event.id,
            errorMessage,
            event.retryCount,
            event.maxRetries,
          );
        }
      }

      if (successCount > 0 || failCount > 0) {
        this.logger.log(
          `Outbox: published ${successCount}, failed ${failCount}`,
        );
      }
    } catch (error) {
      this.logger.error('Failed to process outbox events', error);
    } finally {
      await this.redis.releaseLock(`${this.LOCK_KEY}:publish`, lockValue);
    }
  }

  /**
   * 每天凌晨3点清理已发布的事件（保留7天）
   */
  @Cron('0 3 * * *')
  async cleanupPublishedEvents(): Promise<void> {
    const lockValue = await this.redis.acquireLock(
      `${this.LOCK_KEY}:cleanup`,
      300,
    );
    if (!lockValue) {
      return;
    }

    try {
      const sevenDaysAgo = new Date();
      sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);

      const deleted = await this.outboxRepository.deletePublished(sevenDaysAgo);
      if (deleted > 0) {
        this.logger.log(`Cleaned up ${deleted} published outbox events`);
      }
    } catch (error) {
      this.logger.error('Failed to cleanup published events', error);
    } finally {
      await this.redis.releaseLock(`${this.LOCK_KEY}:cleanup`, lockValue);
    }
  }
}
