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 {
  MinuteBurnExecutedEvent,
  MinuteBurnExecutedPayload,
  BurnExecutedEvent,
  BurnExecutedPayload,
} from '../events/burn.event';

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

/**
 * 销毁事件消费者
 * 监听 trading-service 发布的销毁事件，从 SHARE_POOL_A 扣减
 */
@Controller()
export class BurnConsumer implements OnModuleInit {
  private readonly logger = new Logger(BurnConsumer.name);

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

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

  /**
   * 处理每分钟定时销毁事件
   * Topic: trading.burns
   * EventType: burn.minute-executed
   */
  @EventPattern('trading.burns')
  async handleBurnEvent(@Payload() message: any): Promise<void> {
    // 解析消息格式（Outbox 发布的格式）
    const eventData = message.value || message;
    const eventId = eventData.eventId || message.eventId;
    const eventType = eventData.eventType;

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

    // 根据事件类型分发处理
    if (eventType === 'burn.minute-executed') {
      await this.handleMinuteBurn(eventId, eventData as MinuteBurnExecutedEvent);
    } else if (eventType === 'burn.executed') {
      await this.handleSellBurn(eventId, eventData as BurnExecutedEvent);
    } else {
      this.logger.debug(`Unknown burn event type: ${eventType}, skipping`);
    }
  }

  /**
   * 处理每分钟定时销毁
   */
  private async handleMinuteBurn(
    eventId: string,
    event: MinuteBurnExecutedEvent,
  ): Promise<void> {
    this.logger.debug(`Processing minute burn event: ${eventId}`);

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

    try {
      const payload = event.payload;
      const burnAmount = new Decimal(payload.burnAmount);

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

      // 从 SHARE_POOL_A 扣减销毁量
      await this.poolAccountService.deductFromSharePoolAForBurn(burnAmount, {
        burnMinute: new Date(payload.burnMinute),
        burnRecordId: payload.burnRecordId,
        sourceType: 'MINUTE_BURN',
      });

      // 标记为已处理
      await this.markEventProcessed(eventId, 'burn.minute-executed');

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

  /**
   * 处理卖出销毁
   */
  private async handleSellBurn(
    eventId: string,
    event: BurnExecutedEvent,
  ): Promise<void> {
    this.logger.debug(`Processing sell burn event: ${eventId}`);

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

    try {
      const payload = event.payload;
      const burnAmount = new Decimal(payload.burnAmount);

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

      // 从 SHARE_POOL_A 扣减销毁量
      await this.poolAccountService.deductFromSharePoolAForBurn(burnAmount, {
        burnMinute: new Date(payload.executedAt),
        burnRecordId: payload.burnRecordId,
        sourceType: 'SELL_BURN',
        sourceAccountSeq: payload.sourceAccountSeq,
        sourceOrderNo: payload.sourceOrderNo,
      });

      // 标记为已处理
      await this.markEventProcessed(eventId, 'burn.executed');

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

  /**
   * 幂等性检查 - Redis + DB 双重检查
   */
  private async isEventProcessed(eventId: string): Promise<boolean> {
    const redisKey = `processed-event:burn:${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: 'trading-service',
    });

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