import { Injectable, Logger, Inject, OnModuleInit } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { EventPublisherService } from '@/infrastructure/kafka/event-publisher.service';
import {
  OUTBOX_EVENT_REPOSITORY,
  IOutboxEventRepository,
  OutboxEventStatus,
} from '@/domain/repositories/outbox-event.repository.interface';
import {
  DEPOSIT_TRANSACTION_REPOSITORY,
  IDepositTransactionRepository,
} from '@/domain/repositories/deposit-transaction.repository.interface';

/**
 * Outbox 发布服务
 *
 * 定时扫描 outbox_events 表，将待发送的事件发布到 Kafka。
 * 实现发件箱模式（Outbox Pattern），保证事件的可靠投递。
 */
@Injectable()
export class OutboxPublisherService implements OnModuleInit {
  private readonly logger = new Logger(OutboxPublisherService.name);

  // 发送超时时间（秒）- 超过此时间未收到 ACK 则重发
  private readonly SENT_TIMEOUT_SECONDS = 300; // 5 分钟

  // 清理已确认事件的天数
  private readonly CLEANUP_DAYS = 7;

  constructor(
    private readonly eventPublisher: EventPublisherService,
    @Inject(OUTBOX_EVENT_REPOSITORY)
    private readonly outboxRepo: IOutboxEventRepository,
    @Inject(DEPOSIT_TRANSACTION_REPOSITORY)
    private readonly depositRepo: IDepositTransactionRepository,
  ) {}

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

  /**
   * 定时发布待发送事件（每5秒）
   */
  @Cron(CronExpression.EVERY_5_SECONDS)
  async publishPendingEvents(): Promise<void> {
    try {
      const pendingEvents = await this.outboxRepo.findPendingEvents(50);

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

      this.logger.debug(`Found ${pendingEvents.length} pending events to publish`);

      for (const event of pendingEvents) {
        try {
          // 发送到 Kafka
          await this.eventPublisher.publishRaw({
            eventId: `outbox-${event.id}`,
            eventType: event.eventType,
            occurredAt: event.createdAt,
            payload: event.payload,
          });

          // 标记为已发送
          await this.outboxRepo.markAsSent(event.id);

          this.logger.log(
            `Published event ${event.id}: ${event.eventType} for ${event.aggregateType}:${event.aggregateId}`,
          );
        } catch (error) {
          const errorMessage = error instanceof Error ? error.message : 'Unknown error';
          this.logger.error(`Failed to publish event ${event.id}: ${errorMessage}`);

          // 记录失败，安排重试
          await this.outboxRepo.recordFailure(event.id, errorMessage);
        }
      }
    } catch (error) {
      this.logger.error('Error in publishPendingEvents:', error);
    }
  }

  /**
   * 定时检查超时未确认的事件（每分钟）
   * 将 SENT 状态但超时的事件重置为 PENDING
   */
  @Cron(CronExpression.EVERY_MINUTE)
  async checkUnackedEvents(): Promise<void> {
    try {
      const unackedEvents = await this.outboxRepo.findUnackedEvents(
        this.SENT_TIMEOUT_SECONDS,
        50,
      );

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

      this.logger.warn(`Found ${unackedEvents.length} unacked events, will retry`);

      for (const event of unackedEvents) {
        // 记录超时失败，触发重试逻辑
        await this.outboxRepo.recordFailure(event.id, 'ACK timeout');
        this.logger.warn(
          `Event ${event.id} ACK timeout, scheduled for retry (attempt ${event.retryCount + 1})`,
        );
      }
    } catch (error) {
      this.logger.error('Error in checkUnackedEvents:', error);
    }
  }

  /**
   * 定时清理已确认的旧事件（每天凌晨3点）
   */
  @Cron('0 3 * * *')
  async cleanupOldEvents(): Promise<void> {
    try {
      const count = await this.outboxRepo.cleanupAckedEvents(this.CLEANUP_DAYS);
      if (count > 0) {
        this.logger.log(`Cleaned up ${count} old ACKED events`);
      }
    } catch (error) {
      this.logger.error('Error in cleanupOldEvents:', error);
    }
  }

  /**
   * 处理 ACK 确认
   * 当收到 wallet-service 的确认事件时调用
   */
  async handleAck(aggregateType: string, aggregateId: string, eventType: string): Promise<void> {
    try {
      await this.outboxRepo.markAsAckedByAggregateId(aggregateType, aggregateId, eventType);

      // 同时更新 deposit_transactions 表的 notified_at
      if (aggregateType === 'DepositTransaction') {
        const depositId = BigInt(aggregateId);
        const deposit = await this.depositRepo.findById(depositId);
        if (deposit) {
          deposit.markAsNotified();
          await this.depositRepo.save(deposit);
          this.logger.log(`Deposit ${aggregateId} marked as notified`);
        }
      }
    } catch (error) {
      this.logger.error(`Error handling ACK for ${aggregateType}:${aggregateId}:`, error);
    }
  }
}
