import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { PrismaService } from '@/infrastructure/persistence/prisma/prisma.service';
import { KafkaProducerService } from '@/infrastructure/kafka/kafka-producer.service';
import { RedisService } from '@/infrastructure/redis/redis.service';
import { OutboxStatus } from '@prisma/client';

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

  constructor(
    private readonly prisma: PrismaService,
    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.prisma.outboxEvent.findMany({
        where: {
          status: OutboxStatus.PENDING,
          OR: [{ nextRetryAt: null }, { nextRetryAt: { lte: new Date() } }],
        },
        orderBy: { createdAt: 'asc' },
        take: 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,
            value: {
              eventId: event.id.toString(),
              aggregateType: event.aggregateType,
              aggregateId: event.aggregateId,
              eventType: event.eventType,
              payload: event.payload,
              createdAt: event.createdAt.toISOString(),
            },
          });

          // 标记为已发布
          await this.prisma.outboxEvent.update({
            where: { id: event.id },
            data: {
              status: OutboxStatus.PUBLISHED,
              publishedAt: new Date(),
            },
          });

          successCount++;
        } catch (error) {
          failCount++;
          const errorMessage =
            error instanceof Error ? error.message : 'Unknown error';
          this.logger.error(`Failed to publish event ${event.id}`, error);

          // 更新重试信息（指数退避，最大3小时）
          const newRetryCount = event.retryCount + 1;
          const shouldFail = newRetryCount >= event.maxRetries;

          // 指数退避: 30s, 60s, 120s, 240s, 480s, 960s, 1920s, 3840s, 7680s, 10800s (最大3小时)
          const baseDelayMs = 30000; // 30 seconds
          const maxDelayMs = 3 * 60 * 60 * 1000; // 3 hours
          const delayMs = Math.min(
            baseDelayMs * Math.pow(2, newRetryCount - 1),
            maxDelayMs,
          );

          await this.prisma.outboxEvent.update({
            where: { id: event.id },
            data: {
              retryCount: newRetryCount,
              lastError: errorMessage,
              status: shouldFail ? OutboxStatus.FAILED : OutboxStatus.PENDING,
              nextRetryAt: shouldFail ? null : new Date(Date.now() + delayMs),
            },
          });
        }
      }

      if (successCount > 0 || failCount > 0) {
        this.logger.log(
          `Published ${successCount} outbox events, ${failCount} failed`,
        );
      }
    } 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 result = await this.prisma.outboxEvent.deleteMany({
        where: {
          status: OutboxStatus.PUBLISHED,
          publishedAt: { lt: sevenDaysAgo },
        },
      });

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

  /**
   * 每小时检查并重置长时间失败的事件
   */
  @Cron('0 * * * *')
  async resetStaleFailedEvents(): Promise<void> {
    try {
      const oneDayAgo = new Date();
      oneDayAgo.setDate(oneDayAgo.getDate() - 1);

      // 将超过1天的失败事件重置为待处理，给予重试机会
      const result = await this.prisma.outboxEvent.updateMany({
        where: {
          status: OutboxStatus.FAILED,
          createdAt: { gt: oneDayAgo },
        },
        data: {
          status: OutboxStatus.PENDING,
          retryCount: 0,
          nextRetryAt: new Date(),
        },
      });

      if (result.count > 0) {
        this.logger.log(`Reset ${result.count} stale failed events for retry`);
      }
    } catch (error) {
      this.logger.error('Failed to reset stale failed events', error);
    }
  }
}
