import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { Cron, CronExpression } 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';

@Injectable()
export class OutboxScheduler implements OnModuleInit {
  private readonly logger = new Logger(OutboxScheduler.name);
  private readonly LOCK_KEY = 'mining-wallet:outbox:scheduler:lock';
  private readonly LOCK_TTL = 30; // seconds

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

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

  @Cron(CronExpression.EVERY_5_SECONDS)
  async processOutboxEvents() {
    const lockValue = await this.redis.acquireLock(this.LOCK_KEY, this.LOCK_TTL);
    if (!lockValue) {
      return; // Another instance is processing
    }

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

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

      this.logger.debug(`Processing ${events.length} outbox events`);

      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(),
            },
            headers: {
              'event-type': event.eventType,
              'aggregate-type': event.aggregateType,
            },
          });

          await this.outboxRepo.markAsPublished(event.id);
          this.logger.debug(`Published event: ${event.id} (${event.eventType})`);
        } catch (error) {
          const errorMessage = error instanceof Error ? error.message : 'Unknown error';
          this.logger.error(`Failed to publish event ${event.id}: ${errorMessage}`);

          await this.outboxRepo.markAsFailed(
            event.id,
            errorMessage,
            event.retryCount,
            event.maxRetries,
          );
        }
      }
    } finally {
      await this.redis.releaseLock(this.LOCK_KEY, lockValue);
    }
  }

  @Cron(CronExpression.EVERY_HOUR)
  async cleanupOldEvents() {
    const lockValue = await this.redis.acquireLock(`${this.LOCK_KEY}:cleanup`, 60);
    if (!lockValue) {
      return;
    }

    try {
      // 删除 7 天前已发布的事件
      const before = new Date();
      before.setDate(before.getDate() - 7);

      const deletedCount = await this.outboxRepo.deletePublished(before);

      if (deletedCount > 0) {
        this.logger.log(`Cleaned up ${deletedCount} old outbox events`);
      }
    } finally {
      await this.redis.releaseLock(`${this.LOCK_KEY}:cleanup`, lockValue);
    }
  }
}
