import { Controller, Logger, OnModuleInit } from '@nestjs/common';
import { EventPattern, Payload } from '@nestjs/microservices';
import { ConfigService } from '@nestjs/config';
import Decimal from 'decimal.js';
import { RedisService } from '../../redis/redis.service';
import { ProcessedEventRepository } from '../../persistence/repositories/processed-event.repository';
import { UserWalletService } from '../../../application/services/user-wallet.service';
import { PoolAccountService } from '../../../application/services/pool-account.service';
import {
  ManualMiningCompletedEvent,
  ManualMiningCompletedPayload,
} from '../events/manual-mining.event';

// 24小时 TTL（秒）- 手工操作的幂等性缓存更久
const IDEMPOTENCY_TTL_SECONDS = 24 * 60 * 60;

/**
 * 手工补发挖矿事件消费者
 * 监听 mining-service 发布的手工补发事件，更新用户钱包余额
 */
@Controller()
export class ManualMiningConsumer implements OnModuleInit {
  private readonly logger = new Logger(ManualMiningConsumer.name);
  private miningServiceUrl: string;

  constructor(
    private readonly redis: RedisService,
    private readonly processedEventRepo: ProcessedEventRepository,
    private readonly userWalletService: UserWalletService,
    private readonly poolAccountService: PoolAccountService,
    private readonly configService: ConfigService,
  ) {
    this.miningServiceUrl = this.configService.get<string>(
      'MINING_SERVICE_URL',
      'http://localhost:3021',
    );
  }

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

  /**
   * 处理手工补发挖矿事件
   * Topic: mining.manual-mining.completed
   */
  @EventPattern('mining.manual-mining.completed')
  async handleManualMiningCompleted(@Payload() message: any): Promise<void> {
    // 解析消息格式（Outbox 发布的格式可能嵌套在 payload 中）
    let payload: ManualMiningCompletedPayload;
    let eventId: string;

    if (message.payload && typeof message.payload === 'object') {
      // Outbox 格式: { eventId, eventType, payload: { ... } }
      payload = message.payload;
      eventId = message.aggregateId || payload.eventId;
    } else if (message.eventId) {
      // 直接格式
      payload = message;
      eventId = message.eventId;
    } else {
      this.logger.warn('Received invalid manual mining event format, skipping');
      return;
    }

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

    this.logger.log(`Processing manual mining event: ${eventId}, account: ${payload.accountSequence}`);

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

    try {
      await this.processManualMining(eventId, payload);

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

      // 回调 mining-service 标记已同步
      await this.notifyMiningServiceSynced(eventId);

      this.logger.log(
        `Manual mining processed: account=${payload.accountSequence}, amount=${payload.amount}, operator=${payload.operatorName}`,
      );
    } catch (error) {
      this.logger.error(
        `Failed to process manual mining for account ${payload.accountSequence}`,
        error instanceof Error ? error.stack : error,
      );
      throw error; // 让 Kafka 重试
    }
  }

  /**
   * 处理手工补发：
   * 1. 从 SHARE_POOL_A 扣减
   * 2. 更新用户钱包余额
   */
  private async processManualMining(
    eventId: string,
    payload: ManualMiningCompletedPayload,
  ): Promise<void> {
    const amount = new Decimal(payload.amount);

    if (amount.isZero()) {
      this.logger.warn('Zero manual mining amount, skipping');
      return;
    }

    // 1. 从 SHARE_POOL_A 扣减（与正常挖矿来源一致）
    await this.poolAccountService.deductFromSharePoolA(amount, {
      referenceId: eventId,
      referenceType: 'MANUAL_MINING',
      counterpartyAccountSeq: payload.accountSequence,
      memo: `手工补发挖矿分配给用户[${payload.accountSequence}] - 操作人:${payload.operatorName}`,
    });

    this.logger.debug(
      `Deducted ${amount.toFixed(8)} from SHARE_POOL_A for manual mining`,
    );

    // 2. 更新用户钱包余额
    await this.userWalletService.receiveManualMiningReward(
      payload.accountSequence,
      amount,
      {
        recordId: eventId,
        adoptionDate: payload.adoptionDate,
        effectiveDate: payload.effectiveDate,
        totalSeconds: payload.totalSeconds,
        contributionRatio: payload.contributionRatio,
        operatorId: payload.operatorId,
        operatorName: payload.operatorName,
        reason: payload.reason,
      },
    );

    this.logger.debug(
      `Added ${amount.toFixed(8)} to user wallet for account ${payload.accountSequence}`,
    );
  }

  /**
   * 回调 mining-service 标记已同步
   */
  private async notifyMiningServiceSynced(recordId: string): Promise<void> {
    try {
      const response = await fetch(
        `${this.miningServiceUrl}/admin/manual-mining/mark-synced/${recordId}`,
        {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
        },
      );

      if (!response.ok) {
        this.logger.warn(`Failed to notify mining-service: ${response.status}`);
      } else {
        this.logger.debug(`Notified mining-service that record ${recordId} is synced`);
      }
    } catch (error) {
      // 回调失败不影响主流程
      this.logger.warn(
        `Failed to notify mining-service: ${error instanceof Error ? error.message : error}`,
      );
    }
  }

  /**
   * 幂等性检查 - Redis + DB 双重检查
   */
  private async isEventProcessed(eventId: string): Promise<boolean> {
    const redisKey = `processed-event:manual-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:manual-mining:${eventId}`;
    await this.redis.set(redisKey, '1', IDEMPOTENCY_TTL_SECONDS);
  }
}
