import { Controller, Logger, OnModuleInit } from '@nestjs/common';
import { EventPattern, Payload } from '@nestjs/microservices';
import Decimal from 'decimal.js';
import { RedisService } from '../../redis/redis.service';
import { ProcessedEventRepository } from '../../persistence/repositories/processed-event.repository';
import { PoolAccountService } from '../../../application/services/pool-account.service';
import {
  MiningMinuteDistributedEvent,
  MiningMinuteDistributedPayload,
} from '../events/mining-distribution.event';

// 4小时 TTL（秒）
const IDEMPOTENCY_TTL_SECONDS = 4 * 60 * 60;

/**
 * 挖矿分配事件消费者
 * 监听 mining-service 发布的挖矿分配事件，扣减积分股池余额
 */
@Controller()
export class MiningDistributionConsumer implements OnModuleInit {
  private readonly logger = new Logger(MiningDistributionConsumer.name);

  constructor(
    private readonly redis: RedisService,
    private readonly processedEventRepo: ProcessedEventRepository,
    private readonly poolAccountService: PoolAccountService,
  ) {}

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

  /**
   * 处理挖矿分配事件
   * Topic: mining.distribution.completed
   */
  @EventPattern('mining.distribution.completed')
  async handleMiningDistributed(@Payload() message: any): Promise<void> {
    // 解析消息格式（Outbox 发布的格式）
    const event: MiningMinuteDistributedEvent = message.value || message;
    const eventId = event.eventId || message.eventId;

    if (!eventId) {
      this.logger.warn('Received event without eventId, skipping');
      return;
    }

    this.logger.debug(`Processing mining distribution event: ${eventId}`);

    // 幂等性检查
    if (await this.isEventProcessed(eventId)) {
      this.logger.debug(`Event ${eventId} already processed, skipping`);
      return;
    }

    try {
      await this.processDistribution(event.payload);

      // 标记为已处理
      await this.markEventProcessed(eventId, event.eventType);

      this.logger.log(
        `Mining distribution processed: minute=${event.payload.miningMinute}, amount=${event.payload.totalDistributed}`,
      );
    } catch (error) {
      // 余额不足时不重试，记录警告并跳过（避免无限重试）
      if (error instanceof Error && error.message.includes('Insufficient pool balance')) {
        this.logger.warn(
          `Insufficient SHARE_POOL_B balance for mining distribution ${eventId}, skipping. ${error.message}`,
        );
        await this.markEventProcessed(eventId, 'mining.distribution:skipped:insufficient_balance');
        return;
      }
      this.logger.error(
        `Failed to process mining distribution for minute ${event.payload.miningMinute}`,
        error instanceof Error ? error.stack : error,
      );
      throw error; // 让 Kafka 重试
    }
  }

  /**
   * 处理分配：从 SHARE_POOL_B 扣减分配量
   */
  private async processDistribution(
    payload: MiningMinuteDistributedPayload,
  ): Promise<void> {
    const amount = new Decimal(payload.totalDistributed);

    if (amount.isZero()) {
      this.logger.debug('Zero distribution amount, skipping');
      return;
    }

    // 从 SHARE_POOL_B 扣减（挖矿分配来源）
    await this.poolAccountService.deductFromSharePoolB(amount, {
      miningMinute: new Date(payload.miningMinute),
      participantCount: payload.participantCount,
      referenceId: payload.miningMinute,
    });

    this.logger.debug(
      `Deducted ${amount.toFixed(8)} from SHARE_POOL_B for minute ${payload.miningMinute}`,
    );
  }

  /**
   * 幂等性检查 - Redis + DB 双重检查
   */
  private async isEventProcessed(eventId: string): Promise<boolean> {
    const redisKey = `processed-event:mining:${eventId}`;

    // 1. 先检查 Redis 缓存（快速路径）
    const cached = await this.redis.get(redisKey);
    if (cached) return true;

    // 2. 检查数据库
    const dbRecord = await this.processedEventRepo.findByEventId(eventId);
    if (dbRecord) {
      // 回填 Redis 缓存
      await this.redis.set(redisKey, '1', IDEMPOTENCY_TTL_SECONDS);
      return true;
    }

    return false;
  }

  /**
   * 标记事件为已处理
   */
  private async markEventProcessed(
    eventId: string,
    eventType: string,
  ): Promise<void> {
    // 1. 写入数据库
    await this.processedEventRepo.create({
      eventId,
      eventType,
      sourceService: 'mining-service',
    });

    // 2. 写入 Redis 缓存
    const redisKey = `processed-event:mining:${eventId}`;
    await this.redis.set(redisKey, '1', IDEMPOTENCY_TTL_SECONDS);
  }
}
