import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { KafkaService, WalletServiceClient } from '../../infrastructure';

/**
 * 合同签署事件数据结构
 */
interface ContractSigningEvent {
  eventName: string;
  data: {
    orderNo: string;
    userId: string;
    accountSequence: string;
    treeCount: number;
    totalAmount: number;
    provinceCode: string;
    cityCode: string;
    signedAt?: string;   // contract.signed
    expiredAt?: string;  // contract.expired
    // [2026-02-26] 总部运营成本压力涨价（每棵树加价金额），归总部 (S0000000001)
    priceSupplement?: number;
  };
}

/**
 * 合同签署事件处理器
 * 监听 planting-service 发出的合同签署结果事件
 *
 * 处理流程：
 * - contract.signed → 发送 planting.order.paid → reward-service 正常奖励分配
 * - contract.expired → 发送 planting.order.expired → reward-service 系统账户分配
 *
 * 为什么由 referral-service 转发？
 * 1. 保持原有架构一致性：referral-service 始终是 planting.order.paid 的发布者
 * 2. 确保团队统计已更新后再触发奖励计算（虽然此时统计已在 planting.created 时更新）
 * 3. reward-service 的 handlePlantingOrderPaid 逻辑 100% 不变
 */
@Injectable()
export class ContractSigningHandler implements OnModuleInit {
  private readonly logger = new Logger(ContractSigningHandler.name);

  constructor(
    private readonly kafkaService: KafkaService,
    private readonly walletService: WalletServiceClient,
  ) {}

  async onModuleInit() {
    // 订阅合同签署完成事件
    await this.kafkaService.subscribe(
      'referral-service-contract-signed',
      ['contract.signed'],
      this.handleContractSigned.bind(this),
    );
    this.logger.log('Subscribed to contract.signed events');

    // 订阅合同超时事件
    await this.kafkaService.subscribe(
      'referral-service-contract-expired',
      ['contract.expired'],
      this.handleContractExpired.bind(this),
    );
    this.logger.log('Subscribed to contract.expired events');
  }

  /**
   * 处理合同签署完成事件
   * 转发为 planting.order.paid 事件，触发正常奖励分配
   */
  private async handleContractSigned(
    topic: string,
    message: Record<string, unknown>,
  ): Promise<void> {
    const event = message as unknown as ContractSigningEvent;

    if (event.eventName !== 'contract.signed') {
      return;
    }

    const eventData = event.data;

    this.logger.log(
      `Received contract.signed for order ${eventData.orderNo}, ` +
      `accountSequence: ${eventData.accountSequence}, signedAt: ${eventData.signedAt}`,
    );

    try {
      // 发送 planting.order.paid 事件给 reward-service
      // 这会触发原有的奖励分配流程（100% 不变）
      await this.publishOrderPaidEvent(eventData);

      this.logger.log(
        `Successfully forwarded contract.signed to planting.order.paid for order ${eventData.orderNo}`,
      );
    } catch (error) {
      this.logger.error(
        `Failed to forward contract.signed for order ${eventData.orderNo}:`,
        error,
      );
      throw error;
    }
  }

  /**
   * 处理合同超时事件
   * 转发为 planting.order.expired 事件，触发系统账户奖励分配
   */
  private async handleContractExpired(
    topic: string,
    message: Record<string, unknown>,
  ): Promise<void> {
    const event = message as unknown as ContractSigningEvent;

    if (event.eventName !== 'contract.expired') {
      return;
    }

    const eventData = event.data;

    this.logger.log(
      `Received contract.expired for order ${eventData.orderNo}, ` +
      `accountSequence: ${eventData.accountSequence}, expiredAt: ${eventData.expiredAt}`,
    );

    try {
      // 发送 planting.order.expired 事件给 reward-service
      // 这会触发系统账户奖励分配流程
      await this.publishOrderExpiredEvent(eventData);

      this.logger.log(
        `Successfully forwarded contract.expired to planting.order.expired for order ${eventData.orderNo}`,
      );
    } catch (error) {
      this.logger.error(
        `Failed to forward contract.expired for order ${eventData.orderNo}:`,
        error,
      );
      throw error;
    }
  }

  /**
   * 确认扣款并发送 planting.order.paid 事件给 reward-service
   *
   * 流程：
   * 1. 调用 wallet-service 确认扣款（将冻结资金正式扣除）
   * 2. 发送 planting.order.paid 事件
   */
  private async publishOrderPaidEvent(
    eventData: ContractSigningEvent['data'],
  ): Promise<void> {
    // 步骤1：确认扣款
    const confirmResult = await this.walletService.confirmPlantingDeduction({
      userId: eventData.userId,
      accountSequence: eventData.accountSequence,
      orderId: eventData.orderNo,
    });

    if (!confirmResult.success) {
      throw new Error(`确认扣款失败: ${confirmResult.error}`);
    }

    this.logger.log(`Deduction confirmed for order ${eventData.orderNo}`);

    // 步骤2：发送 planting.order.paid 事件
    const maxRetries = 3;
    let lastError: Error | null = null;

    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        await this.kafkaService.publish({
          topic: 'planting.order.paid',
          key: eventData.accountSequence,
          value: {
            eventName: 'planting.order.paid',
            data: {
              orderId: eventData.orderNo,
              userId: eventData.userId,
              accountSequence: eventData.accountSequence,
              treeCount: eventData.treeCount,
              provinceCode: eventData.provinceCode,
              cityCode: eventData.cityCode,
              paidAt: eventData.signedAt || new Date().toISOString(),
              priceSupplement: eventData.priceSupplement || 0,
            },
          },
        });

        this.logger.log(
          `Published planting.order.paid for order ${eventData.orderNo}, ` +
          `accountSequence ${eventData.accountSequence}`,
        );
        return;
      } catch (error) {
        lastError = error instanceof Error ? error : new Error(String(error));
        this.logger.warn(
          `Failed to publish planting.order.paid (attempt ${attempt}/${maxRetries}): ${lastError.message}`,
        );

        if (attempt < maxRetries) {
          await this.sleep(1000 * attempt);
        }
      }
    }

    throw new Error(
      `Failed to publish planting.order.paid after ${maxRetries} attempts: ${lastError?.message}`,
    );
  }

  /**
   * 确认扣款并发送 planting.order.expired 事件给 reward-service
   *
   * 流程：
   * 1. 调用 wallet-service 确认扣款（将冻结资金正式扣除）
   * 2. 发送 planting.order.expired 事件触发系统账户奖励分配
   */
  private async publishOrderExpiredEvent(
    eventData: ContractSigningEvent['data'],
  ): Promise<void> {
    // 步骤1：确认扣款（即使合同超时，资金也要正式扣除用于分配）
    const confirmResult = await this.walletService.confirmPlantingDeduction({
      userId: eventData.userId,
      accountSequence: eventData.accountSequence,
      orderId: eventData.orderNo,
    });

    if (!confirmResult.success) {
      throw new Error(`确认扣款失败: ${confirmResult.error}`);
    }

    this.logger.log(`Deduction confirmed for expired order ${eventData.orderNo}`);

    // 步骤2：发送 planting.order.expired 事件
    const maxRetries = 3;
    let lastError: Error | null = null;

    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        await this.kafkaService.publish({
          topic: 'planting.order.expired',
          key: eventData.accountSequence,
          value: {
            eventName: 'planting.order.expired',
            data: {
              orderId: eventData.orderNo,
              userId: eventData.userId,
              accountSequence: eventData.accountSequence,
              treeCount: eventData.treeCount,
              provinceCode: eventData.provinceCode,
              cityCode: eventData.cityCode,
              expiredAt: eventData.expiredAt || new Date().toISOString(),
              priceSupplement: eventData.priceSupplement || 0,
            },
          },
        });

        this.logger.log(
          `Published planting.order.expired for order ${eventData.orderNo}, ` +
          `accountSequence ${eventData.accountSequence}`,
        );
        return;
      } catch (error) {
        lastError = error instanceof Error ? error : new Error(String(error));
        this.logger.warn(
          `Failed to publish planting.order.expired (attempt ${attempt}/${maxRetries}): ${lastError.message}`,
        );

        if (attempt < maxRetries) {
          await this.sleep(1000 * attempt);
        }
      }
    }

    throw new Error(
      `Failed to publish planting.order.expired after ${maxRetries} attempts: ${lastError?.message}`,
    );
  }

  private sleep(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
}
