import { Injectable, Logger } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrePlantingCdcEvent } from '../../infrastructure/kafka/pre-planting-cdc-consumer.service';
import { PrePlantingContributionService, PrePlantingMergeSyncResult } from '../services/pre-planting-contribution.service';

/**
 * 预种合成树 CDC 事件处理器
 *
 * [2026-02-28] 新增：处理 cdc.pre-planting.public.pre_planting_merges 的 CDC 事件
 *
 * === 触发节点 ===
 * planting-service.signContract(mergeNo) 调用后，pre_planting_merges.mining_enabled_at 从 null
 * 变为非 null，Debezium 捕获此 UPDATE 事件，本 handler 处理。
 *
 * === 处理逻辑 ===
 * 1. 过滤：只处理 mining_enabled_at 非空的 UPDATE 事件（合同签署触发）
 * 2. 解析：提取 mergeNo、accountSequence、sourceOrderNos、miningEnabledAt
 * 3. 返回：PrePlantingMergeSyncResult（由 dispatcher 在事务提交后触发算力切换）
 *
 * === 幂等性 ===
 * - CDC 层面：processedCdcEvent 表（sourceTopic + offset 唯一约束，在主 Prisma 事务内）
 * - 业务层面：contribution_records 中 sourceAdoptionId(20B + mergeId) 存在性检查
 * - 最终标记：pre_planting_synced_merges 表（事务提交后 best-effort 写入）
 */
@Injectable()
export class PrePlantingMergeSyncedHandler {
  private readonly logger = new Logger(PrePlantingMergeSyncedHandler.name);

  constructor(
    private readonly contributionService: PrePlantingContributionService,
  ) {}

  /**
   * 处理预种合成树 CDC 事件（在主 PrismaService 事务内执行）
   *
   * 只解析事件，不执行业务逻辑（业务逻辑在 swapAfterCommit 中执行）。
   *
   * @param event CDC 事件
   * @param tx 主 Prisma 事务客户端（由 CDC consumer 提供）
   * @returns 合成结果（null 表示不需要处理）
   */
  async handle(
    event: PrePlantingCdcEvent,
    tx: Prisma.TransactionClient,
  ): Promise<PrePlantingMergeSyncResult | null> {
    const { op, after } = event.payload;

    // 只处理 INSERT（c/r）和 UPDATE（u）；DELETE 忽略
    if (!after || op === 'd') {
      return null;
    }

    // 只处理 mining_enabled_at 非空的事件（合同签署）
    const miningEnabledAtRaw = after.mining_enabled_at ?? after.miningEnabledAt;
    if (!miningEnabledAtRaw) {
      this.logger.debug(
        `[PRE-PLANTING-MERGE] Skipping: mining_enabled_at is null, ` +
        `mergeNo=${after.merge_no ?? after.mergeNo ?? 'unknown'}`,
      );
      return null;
    }

    const mergeNo = after.merge_no || after.mergeNo;
    if (!mergeNo) {
      this.logger.warn(`[PRE-PLANTING-MERGE] Missing merge_no in CDC event`);
      return null;
    }

    const mergeId = after.merge_id || after.id;
    if (!mergeId) {
      this.logger.warn(`[PRE-PLANTING-MERGE] Missing merge_id in CDC event for mergeNo=${mergeNo}`);
      return null;
    }

    const accountSequence = after.account_sequence || after.accountSequence;
    if (!accountSequence) {
      this.logger.warn(`[PRE-PLANTING-MERGE] Missing account_sequence for mergeNo=${mergeNo}`);
      return null;
    }

    // 解析 source_order_nos（JSONB 列，Debezium 以字符串或对象传输）
    const rawSourceOrderNos = after.source_order_nos ?? after.sourceOrderNos;
    let sourceOrderNos: string[];
    try {
      if (typeof rawSourceOrderNos === 'string') {
        sourceOrderNos = JSON.parse(rawSourceOrderNos);
      } else if (Array.isArray(rawSourceOrderNos)) {
        sourceOrderNos = rawSourceOrderNos;
      } else {
        this.logger.warn(
          `[PRE-PLANTING-MERGE] Invalid source_order_nos for mergeNo=${mergeNo}: ` +
          `${JSON.stringify(rawSourceOrderNos)}`,
        );
        return null;
      }
    } catch (e) {
      this.logger.warn(`[PRE-PLANTING-MERGE] Failed to parse source_order_nos for mergeNo=${mergeNo}`, e);
      return null;
    }

    // 解析 mining_enabled_at（可能是 ISO 字符串或 epoch ms 整数，new Date() 均可处理）
    const miningEnabledAt = new Date(miningEnabledAtRaw);

    this.logger.log(
      `[PRE-PLANTING-MERGE] Detected contract signed: mergeNo=${mergeNo}, ` +
      `accountSequence=${accountSequence}, miningEnabledAt=${miningEnabledAt.toISOString()}, ` +
      `sourceOrders=${sourceOrderNos.join(',')}`,
    );

    return {
      mergeNo,
      mergeId: BigInt(mergeId),
      accountSequence,
      sourceOrderNos,
      miningEnabledAt,
      sourceTopic: event.topic,
      sourceOffset: event.offset,
    };
  }

  /**
   * 事务提交后的算力切换回调
   *
   * 由 PrePlantingCdcDispatcher 在主 Prisma 事务成功提交后调用。
   * 失败时记录错误但不影响 CDC offset 提交（下次重试时幂等保护）。
   */
  async swapAfterCommit(result: PrePlantingMergeSyncResult): Promise<void> {
    this.logger.log(`[PRE-PLANTING-MERGE] Triggering contribution swap: mergeNo=${result.mergeNo}`);
    try {
      await this.contributionService.swapContributionForMerge(result);
      this.logger.log(`[PRE-PLANTING-MERGE] Contribution swap completed: mergeNo=${result.mergeNo}`);
    } catch (error) {
      // 算力切换失败不阻断 CDC 消费，下次 Kafka 重试时幂等保护会防止重复执行
      this.logger.error(
        `[PRE-PLANTING-MERGE] Contribution swap failed: mergeNo=${result.mergeNo}`,
        error,
      );
    }
  }
}
