import { Injectable, Logger } from '@nestjs/common';
import Decimal from 'decimal.js';
import { ContributionCalculatorService, ContributionDistributionResult } from '@/domain/services/contribution-calculator.service';
import { ContributionAccountRepository } from '@/infrastructure/persistence/repositories/contribution-account.repository';
import { ContributionRecordRepository } from '@/infrastructure/persistence/repositories/contribution-record.repository';
import { SyncedDataRepository } from '@/infrastructure/persistence/repositories/synced-data.repository';
import { UnallocatedContributionRepository } from '@/infrastructure/persistence/repositories/unallocated-contribution.repository';
import { SystemAccountRepository } from '@/infrastructure/persistence/repositories/system-account.repository';
import { OutboxRepository } from '@/infrastructure/persistence/repositories/outbox.repository';
import { UnitOfWork } from '@/infrastructure/persistence/unit-of-work/unit-of-work';
import { ContributionAccountAggregate, ContributionSourceType } from '@/domain/aggregates/contribution-account.aggregate';
import { ContributionRecordAggregate } from '@/domain/aggregates/contribution-record.aggregate';
import { ContributionAmount } from '@/domain/value-objects/contribution-amount.vo';
import { SyncedAdoption, SyncedReferral } from '@/domain/repositories/synced-data.repository.interface';
import { ContributionRateService } from '@/application/services/contribution-rate.service';
import { ContributionDistributionPublisherService } from '@/application/services/contribution-distribution-publisher.service';
import { BonusClaimService } from '@/application/services/bonus-claim.service';
import { ContributionRecordSyncedEvent, ContributionAccountUpdatedEvent, SystemAccountSyncedEvent, SystemContributionRecordCreatedEvent, UnallocatedContributionSyncedEvent } from '@/domain/events';
import { PrePlantingPrismaService } from '../../infrastructure/prisma/pre-planting-prisma.service';
import {
  PRE_PLANTING_SOURCE_ID_OFFSET,
  PRE_PLANTING_PORTION_DIVISOR,
  PRE_PLANTING_MERGE_SOURCE_ID_OFFSET,
} from '../../domain/constants';

/**
 * 预种合成树算力切换的输入参数
 * 由 PrePlantingMergeSyncedHandler 解析 CDC 事件后传入
 */
export interface PrePlantingMergeSyncResult {
  mergeNo: string;
  mergeId: bigint;
  accountSequence: string;
  sourceOrderNos: string[];
  miningEnabledAt: Date;
  sourceTopic: string;
  sourceOffset: bigint;
}

/**
 * 预种算力计算服务
 *
 * [2026-02-17] 新增：复用现有领域计算器，以 1/10 贡献值处理预种份额
 * [2026-03-04] 更新：预种方案从「5份合1树」调整为「10份合1树」，每份算力从 rate/5 改为 rate/10
 *
 * === 核心设计 ===
 * - 构建虚拟 SyncedAdoption 对象（treeCount=portionCount, contributionPerTree=rate/10）
 * - 调用现有 ContributionCalculatorService（领域服务）计算分配
 * - 结果写入现有 contribution_accounts/contribution_records 表（挖矿系统可见）
 * - 使用 PRE_PLANTING_SOURCE_ID_OFFSET (10B) 偏移，避免与正常认种 ID 冲突
 *
 * === 与现有 ContributionCalculationService 的区别 ===
 * - 不更新 NetworkAdoptionProgress（预种不推高全网算力系数）
 * - sourceAdoptionId 使用 10B 偏移
 * - 追踪表使用独立的 PrePlantingPrismaService
 * - unlock 逻辑通过 synced_adoptions marker 实现（不直接修改现有方法）
 */
@Injectable()
export class PrePlantingContributionService {
  private readonly logger = new Logger(PrePlantingContributionService.name);
  private readonly domainCalculator = new ContributionCalculatorService();

  constructor(
    private readonly contributionAccountRepository: ContributionAccountRepository,
    private readonly contributionRecordRepository: ContributionRecordRepository,
    private readonly syncedDataRepository: SyncedDataRepository,
    private readonly unallocatedContributionRepository: UnallocatedContributionRepository,
    private readonly systemAccountRepository: SystemAccountRepository,
    private readonly outboxRepository: OutboxRepository,
    private readonly unitOfWork: UnitOfWork,
    private readonly distributionPublisher: ContributionDistributionPublisherService,
    private readonly contributionRateService: ContributionRateService,
    private readonly bonusClaimService: BonusClaimService,
    private readonly prePlantingPrisma: PrePlantingPrismaService,
  ) {}

  /**
   * 为预种订单计算并分配算力
   *
   * @param orderNo 预种订单业务单号（如 PPLMM...），通过 accountSequence 关联，不依赖 BigInt 自增 ID
   */
  async calculateForPrePlantingOrder(orderNo: string): Promise<void> {
    // 从预种追踪表获取订单数据（以 orderNo 为唯一键）
    const order = await this.prePlantingPrisma.prePlantingSyncedOrder.findUnique({
      where: { orderNo },
    });

    if (!order) {
      throw new Error(`Pre-planting order not found: ${orderNo}`);
    }

    if (order.status !== 'PAID') {
      this.logger.debug(`Pre-planting order ${orderNo} status=${order.status}, skipping`);
      return;
    }

    // 生成偏移后的 sourceAdoptionId（用存储的 originalOrderId 保证唯一性，仅内部使用）
    const sourceAdoptionId = PRE_PLANTING_SOURCE_ID_OFFSET + order.originalOrderId;

    // 检查是否已经处理过（避免重复计算）
    const exists = await this.contributionRecordRepository.existsBySourceAdoptionId(sourceAdoptionId);
    if (exists) {
      this.logger.debug(`Pre-planting order ${orderNo} already processed, skipping`);
      return;
    }

    // 获取当日每棵树贡献值（预种份额 = 1/10，10份合1棵树）
    const adoptionDate = order.paidAt || order.createdAt;
    let contributionPerTree = new Decimal('22617');
    try {
      contributionPerTree = await this.contributionRateService.getContributionPerTree(adoptionDate);
    } catch (error) {
      this.logger.warn(`Failed to get contribution rate, using default`, error);
    }
    const contributionPerPortion = contributionPerTree.div(PRE_PLANTING_PORTION_DIVISOR);

    // 构建虚拟 SyncedAdoption 对象
    const virtualAdoption: SyncedAdoption = {
      id: BigInt(0),
      originalAdoptionId: sourceAdoptionId,
      accountSequence: order.accountSequence,
      treeCount: Number(order.portionCount), // 每份 = 1 个虚拟 "树"
      adoptionDate,
      status: 'MINING_ENABLED',
      selectedProvince: order.provinceCode,
      selectedCity: order.cityCode,
      contributionPerTree: contributionPerPortion, // 1/10 贡献值
      sourceSequenceNum: BigInt(0),
      syncedAt: new Date(),
      contributionDistributed: false,
      contributionDistributedAt: null,
      createdAt: order.createdAt,
    };

    // 获取推荐关系链
    const userReferral = await this.syncedDataRepository.findSyncedReferralByAccountSequence(
      order.accountSequence,
    );

    if (!userReferral) {
      this.logger.warn(
        `[PRE-PLANTING] Deferring order ${orderNo}: ` +
        `referral for ${order.accountSequence} not yet synced`,
      );
      return;
    }

    // 获取上线链条（最多15级）
    let ancestorChain: SyncedReferral[] = [];
    if (userReferral.referrerAccountSequence) {
      ancestorChain = await this.syncedDataRepository.findAncestorChain(
        userReferral.referrerAccountSequence,
        15,
      );
    }

    // 获取算力账户
    const adopterAccount = await this.contributionAccountRepository.findByAccountSequence(
      order.accountSequence,
    );
    const ancestorAccountSequences = ancestorChain.map((a) => a.accountSequence);
    const ancestorAccounts = await this.contributionAccountRepository.findByAccountSequences(
      ancestorAccountSequences,
    );

    // 使用领域计算器计算分配
    const result = this.domainCalculator.calculateAdoptionContribution(
      virtualAdoption,
      adopterAccount,
      ancestorChain,
      ancestorAccounts,
    );

    // 在事务中保存分配结果
    await this.unitOfWork.executeInTransaction(async () => {
      await this.saveDistributionResult(result, sourceAdoptionId, order.accountSequence);

      // 更新认种人解锁状态
      await this.updateAdopterUnlockStatus(order.accountSequence);

      // 更新直接上线解锁状态
      if (userReferral.referrerAccountSequence) {
        await this.updateReferrerUnlockStatus(userReferral.referrerAccountSequence);
      }

      // 发布分配结果到 Kafka
      await this.distributionPublisher.publishDistributionResult(
        virtualAdoption,
        result,
        order.provinceCode || 'DEFAULT',
        order.cityCode || 'DEFAULT',
      );
    });

    // 标记预种追踪表为已分配
    try {
      await this.prePlantingPrisma.prePlantingSyncedOrder.update({
        where: { orderNo },
        data: {
          contributionDistributed: true,
          contributionDistributedAt: new Date(),
        },
      });
    } catch (error) {
      this.logger.error(`Failed to mark pre-planting order as distributed`, error);
    }

    this.logger.log(
      `Pre-planting contribution calculated: orderNo=${orderNo}, ` +
      `sourceId=${sourceAdoptionId}, personal=${result.personalRecord.amount.value}, ` +
      `teamLevel=${result.teamLevelRecords.length}, teamBonus=${result.teamBonusRecords.length}`,
    );

    // 注意：不调用 contributionRateService.updateNetworkProgress()
    // 预种份额不推高全网算力系数
  }

  /**
   * 批量处理未分配的预种订单（由调度器调用）
   */
  async processUndistributedOrders(batchSize: number = 50): Promise<number> {
    const orders = await this.prePlantingPrisma.prePlantingSyncedOrder.findMany({
      where: {
        status: 'PAID',
        contributionDistributed: false,
      },
      take: batchSize,
      orderBy: { createdAt: 'asc' },
    });

    let count = 0;
    for (const order of orders) {
      try {
        await this.calculateForPrePlantingOrder(order.orderNo);
        count++;
      } catch (error) {
        this.logger.error(
          `Failed to process pre-planting order ${order.orderNo}`,
          error,
        );
      }
    }
    return count;
  }

  /**
   * 保存分配结果（复制自 ContributionCalculationService.saveDistributionResult）
   *
   * 不修改原方法（private 且参数不同），在此完整复制保存逻辑。
   * 与原版唯一的区别是日志前缀带 [PRE-PLANTING] 标识。
   */
  private async saveDistributionResult(
    result: ContributionDistributionResult,
    sourceAdoptionId: bigint,
    sourceAccountSequence: string,
  ): Promise<void> {
    const savedRecords: ContributionRecordAggregate[] = [];
    const updatedAccountSequences = new Set<string>();

    // 1. 个人算力
    const savedPersonalRecord = await this.contributionRecordRepository.save(result.personalRecord);
    savedRecords.push(savedPersonalRecord);

    let account = await this.contributionAccountRepository.findByAccountSequence(
      result.personalRecord.accountSequence,
    );
    if (!account) {
      account = ContributionAccountAggregate.create(result.personalRecord.accountSequence);
    }
    account.addPersonalContribution(result.personalRecord.amount);
    await this.contributionAccountRepository.save(account);
    updatedAccountSequences.add(result.personalRecord.accountSequence);

    // 2. 团队层级算力
    if (result.teamLevelRecords.length > 0) {
      const savedLevelRecords = await this.contributionRecordRepository.saveMany(result.teamLevelRecords);
      savedRecords.push(...savedLevelRecords);

      for (const record of result.teamLevelRecords) {
        await this.contributionAccountRepository.updateContribution(
          record.accountSequence,
          ContributionSourceType.TEAM_LEVEL,
          record.amount,
          record.levelDepth,
          null,
        );
        updatedAccountSequences.add(record.accountSequence);
      }
    }

    // 3. 团队加成算力
    if (result.teamBonusRecords.length > 0) {
      const savedBonusRecords = await this.contributionRecordRepository.saveMany(result.teamBonusRecords);
      savedRecords.push(...savedBonusRecords);

      for (const record of result.teamBonusRecords) {
        await this.contributionAccountRepository.updateContribution(
          record.accountSequence,
          ContributionSourceType.TEAM_BONUS,
          record.amount,
          null,
          record.bonusTier,
        );
        updatedAccountSequences.add(record.accountSequence);
      }
    }

    const effectiveDate = result.personalRecord.effectiveDate;
    const expireDate = result.personalRecord.expireDate;

    // 4. 未分配算力
    if (result.unallocatedContributions.length > 0) {
      await this.unallocatedContributionRepository.saveMany(
        result.unallocatedContributions.map((u) => ({
          ...u,
          sourceAdoptionId,
          sourceAccountSequence,
          effectiveDate,
          expireDate,
        })),
      );

      const totalUnallocatedAmount = result.unallocatedContributions.reduce(
        (sum, u) => sum.add(u.amount),
        new ContributionAmount(0),
      );
      await this.systemAccountRepository.addContribution('HEADQUARTERS', null, totalUnallocatedAmount);

      for (const unallocated of result.unallocatedContributions) {
        const sourceType = unallocated.type as string;
        const levelDepth = unallocated.levelDepth;

        const savedRecord = await this.systemAccountRepository.saveContributionRecord({
          accountType: 'HEADQUARTERS',
          regionCode: null,
          sourceAdoptionId,
          sourceAccountSequence,
          sourceType,
          levelDepth,
          distributionRate: 0,
          amount: unallocated.amount,
          effectiveDate,
          expireDate: null,
        });

        const recordEvent = new SystemContributionRecordCreatedEvent(
          savedRecord.id,
          'HEADQUARTERS',
          null,
          sourceAdoptionId,
          sourceAccountSequence,
          sourceType as any,
          levelDepth,
          0,
          unallocated.amount.value.toString(),
          effectiveDate,
          null,
          savedRecord.createdAt,
        );
        await this.outboxRepository.save({
          aggregateType: SystemContributionRecordCreatedEvent.AGGREGATE_TYPE,
          aggregateId: savedRecord.id.toString(),
          eventType: SystemContributionRecordCreatedEvent.EVENT_TYPE,
          payload: recordEvent.toPayload(),
        });
      }

      const headquartersAccount = await this.systemAccountRepository.findByTypeAndRegion('HEADQUARTERS', null);
      if (headquartersAccount) {
        const hqEvent = new SystemAccountSyncedEvent(
          'HEADQUARTERS',
          null,
          headquartersAccount.name,
          headquartersAccount.contributionBalance.value.toString(),
          headquartersAccount.createdAt,
        );
        await this.outboxRepository.save({
          aggregateType: SystemAccountSyncedEvent.AGGREGATE_TYPE,
          aggregateId: 'HEADQUARTERS',
          eventType: SystemAccountSyncedEvent.EVENT_TYPE,
          payload: hqEvent.toPayload(),
        });
      }

      for (const unallocated of result.unallocatedContributions) {
        const event = new UnallocatedContributionSyncedEvent(
          sourceAdoptionId,
          sourceAccountSequence,
          unallocated.wouldBeAccountSequence,
          unallocated.type,
          unallocated.amount.value.toString(),
          unallocated.reason,
          effectiveDate,
          expireDate,
        );
        await this.outboxRepository.save({
          aggregateType: UnallocatedContributionSyncedEvent.AGGREGATE_TYPE,
          aggregateId: `${sourceAdoptionId}-${unallocated.type}`,
          eventType: UnallocatedContributionSyncedEvent.EVENT_TYPE,
          payload: event.toPayload(),
        });
      }
    }

    // 5. 系统账户算力
    if (result.systemContributions.length > 0) {
      await this.systemAccountRepository.ensureSystemAccountsExist();

      for (const sys of result.systemContributions) {
        await this.systemAccountRepository.addContribution(sys.accountType, sys.regionCode, sys.amount);

        const savedRecord = await this.systemAccountRepository.saveContributionRecord({
          accountType: sys.accountType,
          regionCode: sys.regionCode,
          sourceAdoptionId,
          sourceAccountSequence,
          sourceType: 'FIXED_RATE',
          levelDepth: null,
          distributionRate: sys.rate.value.toNumber(),
          amount: sys.amount,
          effectiveDate,
          expireDate: null,
        });

        const systemAccount = await this.systemAccountRepository.findByTypeAndRegion(sys.accountType, sys.regionCode);
        if (systemAccount) {
          const event = new SystemAccountSyncedEvent(
            sys.accountType,
            sys.regionCode,
            systemAccount.name,
            systemAccount.contributionBalance.value.toString(),
            systemAccount.createdAt,
          );
          await this.outboxRepository.save({
            aggregateType: SystemAccountSyncedEvent.AGGREGATE_TYPE,
            aggregateId: `${sys.accountType}:${sys.regionCode || 'null'}`,
            eventType: SystemAccountSyncedEvent.EVENT_TYPE,
            payload: event.toPayload(),
          });

          const recordEvent = new SystemContributionRecordCreatedEvent(
            savedRecord.id,
            sys.accountType,
            sys.regionCode,
            sourceAdoptionId,
            sourceAccountSequence,
            'FIXED_RATE',
            null,
            sys.rate.value.toNumber(),
            sys.amount.value.toString(),
            effectiveDate,
            null,
            savedRecord.createdAt,
          );
          await this.outboxRepository.save({
            aggregateType: SystemContributionRecordCreatedEvent.AGGREGATE_TYPE,
            aggregateId: savedRecord.id.toString(),
            eventType: SystemContributionRecordCreatedEvent.EVENT_TYPE,
            payload: recordEvent.toPayload(),
          });
        }
      }
    }

    // 6. 发布算力记录事件
    await this.publishContributionRecordEvents(savedRecords);

    // 7. 发布账户更新事件
    await this.publishUpdatedAccountEvents(updatedAccountSequences);
  }

  private async updateAdopterUnlockStatus(accountSequence: string): Promise<void> {
    const account = await this.contributionAccountRepository.findByAccountSequence(accountSequence);
    if (!account) return;

    if (!account.hasAdopted) {
      account.markAsAdopted();
      await this.contributionAccountRepository.save(account);
      await this.publishContributionAccountUpdatedEvent(account);
    }
  }

  private async updateReferrerUnlockStatus(referrerAccountSequence: string): Promise<void> {
    const account = await this.contributionAccountRepository.findByAccountSequence(referrerAccountSequence);
    if (!account) return;

    // 重新计算直推已认种人数
    // 由于 synced_adoptions 中已有预种 marker 记录，此方法会自然计入预种用户
    const directReferralAdoptedCount = await this.syncedDataRepository.getDirectReferralAdoptedCount(
      referrerAccountSequence,
    );

    const previousCount = account.directReferralAdoptedCount;
    if (directReferralAdoptedCount > previousCount) {
      for (let i = previousCount; i < directReferralAdoptedCount; i++) {
        account.incrementDirectReferralAdoptedCount();
      }
      await this.contributionAccountRepository.save(account);
      await this.publishContributionAccountUpdatedEvent(account);

      this.logger.debug(
        `[PRE-PLANTING] Updated referrer ${referrerAccountSequence}: ` +
        `level=${account.unlockedLevelDepth}, bonus=${account.unlockedBonusTiers}`,
      );

      await this.bonusClaimService.checkAndClaimBonus(
        referrerAccountSequence,
        previousCount,
        directReferralAdoptedCount,
      );
    }
  }

  private async publishContributionRecordEvents(
    savedRecords: ContributionRecordAggregate[],
  ): Promise<void> {
    if (savedRecords.length === 0) return;

    const events = savedRecords.map((record) => {
      const event = new ContributionRecordSyncedEvent(
        record.id!,
        record.accountSequence,
        record.sourceType,
        record.sourceAdoptionId,
        record.sourceAccountSequence,
        record.treeCount,
        record.baseContribution.value.toString(),
        record.distributionRate.value.toString(),
        record.levelDepth,
        record.bonusTier,
        record.amount.value.toString(),
        record.effectiveDate,
        record.expireDate,
        record.isExpired,
        record.createdAt,
      );

      return {
        aggregateType: ContributionRecordSyncedEvent.AGGREGATE_TYPE,
        aggregateId: record.id!.toString(),
        eventType: ContributionRecordSyncedEvent.EVENT_TYPE,
        payload: event.toPayload(),
      };
    });

    await this.outboxRepository.saveMany(events);
  }

  private async publishUpdatedAccountEvents(accountSequences: Set<string>): Promise<void> {
    for (const accountSequence of accountSequences) {
      const account = await this.contributionAccountRepository.findByAccountSequence(accountSequence);
      if (account) {
        await this.publishContributionAccountUpdatedEvent(account);
      }
    }
  }

  /**
   * 预种合成树后算力切换
   *
   * [2026-02-28] 新增：预种合成1棵树，合同签署（mining_enabled_at 写入）后触发
   * [2026-03-04] 更新：合成阈值从 5份 改为 10份
   *
   * === 操作流程 ===
   * 1. 作废全部份额的算力记录（is_expired=true，remark 说明原因，已挖积分不追回）
   * 2. 从认种人账户扣减旧个人算力（保持账户余额准确）
   * 3. 以1棵完整树的算力创建新记录（remark 标注合成来源）
   * 4. 团队分润账户通过 saveDistributionResult 自然更新（净效果≈0，旧=新）
   * 5. 写入 pre_planting_synced_merges 幂等标记（best-effort）
   *
   * === 幂等性 ===
   * 以 contribution_records WHERE sourceAdoptionId = 20B + mergeId 的存在性为主要幂等检查。
   * 若检查通过但 prePlantingSyncedMerge 未写入，下次重试时仍会被幂等检查拦截。
   */
  async swapContributionForMerge(result: PrePlantingMergeSyncResult): Promise<void> {
    const { mergeNo, mergeId, accountSequence, sourceOrderNos, miningEnabledAt } = result;
    const mergeSourceAdoptionId = PRE_PLANTING_MERGE_SOURCE_ID_OFFSET + mergeId;

    // Step 1: 查找源订单的追踪记录
    const sourceOrders = await this.prePlantingPrisma.prePlantingSyncedOrder.findMany({
      where: { orderNo: { in: sourceOrderNos } },
    });

    if (sourceOrders.length === 0) {
      this.logger.warn(
        `[PRE-PLANTING-MERGE] No source orders found in tracking table for merge: ${mergeNo}. ` +
        `Expected orders: ${sourceOrderNos.join(',')}`,
      );
      return;
    }

    if (sourceOrders.length < sourceOrderNos.length) {
      // 理论上不应发生（时序保证 order CDC 先于 merge CDC 到达），
      // 若发生则记录 warn，以已同步的订单为准继续执行（不阻断）
      this.logger.warn(
        `[PRE-PLANTING-MERGE] Partial orders in tracking table for merge ${mergeNo}: ` +
        `found ${sourceOrders.length}/${sourceOrderNos.length}. Proceeding with available records.`,
      );
    }

    // Step 2: 计算份额 sourceAdoptionId 列表（10B 偏移，与 calculateForPrePlantingOrder 一致）
    const portionSourceAdoptionIds = sourceOrders.map(
      (o) => PRE_PLANTING_SOURCE_ID_OFFSET + o.originalOrderId,
    );

    // Step 3: 幂等检查 —— 若新树算力记录已存在，直接跳过
    const alreadyProcessed = await this.contributionRecordRepository.existsBySourceAdoptionId(
      mergeSourceAdoptionId,
    );
    if (alreadyProcessed) {
      this.logger.debug(`[PRE-PLANTING-MERGE] Already swapped (idempotent skip): ${mergeNo}`);
      return;
    }

    // Step 4: 获取合成时的全树算力单价（以 mining_enabled_at 为基准日，不除以5）
    let contributionPerTree = new Decimal('22617');
    try {
      contributionPerTree = await this.contributionRateService.getContributionPerTree(miningEnabledAt);
    } catch (error) {
      this.logger.warn(`[PRE-PLANTING-MERGE] Failed to get contribution rate, using default`, error);
    }

    // Step 5: 构建虚拟 SyncedAdoption（1棵完整树，不÷5）
    const firstOrder = sourceOrders[0];
    const virtualAdoption: SyncedAdoption = {
      id: BigInt(0),
      originalAdoptionId: mergeSourceAdoptionId,
      accountSequence,
      treeCount: 1,
      adoptionDate: miningEnabledAt,
      status: 'MINING_ENABLED',
      selectedProvince: firstOrder.provinceCode || '',
      selectedCity: firstOrder.cityCode || '',
      contributionPerTree,
      sourceSequenceNum: BigInt(0),
      syncedAt: new Date(),
      contributionDistributed: false,
      contributionDistributedAt: null,
      createdAt: miningEnabledAt,
    };

    // Step 6: 获取推荐关系链
    const userReferral = await this.syncedDataRepository.findSyncedReferralByAccountSequence(accountSequence);
    if (!userReferral) {
      throw new Error(
        `[PRE-PLANTING-MERGE] Referral not synced for ${accountSequence}, ` +
        `cannot swap contribution for merge ${mergeNo}`,
      );
    }
    let ancestorChain: SyncedReferral[] = [];
    if (userReferral.referrerAccountSequence) {
      ancestorChain = await this.syncedDataRepository.findAncestorChain(
        userReferral.referrerAccountSequence,
        15,
      );
    }

    // Step 7: 获取算力账户
    const adopterAccount = await this.contributionAccountRepository.findByAccountSequence(accountSequence);
    const ancestorAccounts = await this.contributionAccountRepository.findByAccountSequences(
      ancestorChain.map((a) => a.accountSequence),
    );

    // Step 8: 使用领域计算器计算新树分配（复用现有逻辑）
    const newTreeResult = this.domainCalculator.calculateAdoptionContribution(
      virtualAdoption,
      adopterAccount,
      ancestorChain,
      ancestorAccounts,
    );

    // Step 9: 在事务中执行算力切换（原子操作）
    const expireRemark =
      `预种合成：本份额已合并为1棵树（${mergeNo}），算力转为树级计算，已挖积分不受影响`;
    const mergeRemark =
      `预种合成树：由${sourceOrderNos.length}份份额合并（${mergeNo}）算力升级为整棵树，` +
      `源订单：${sourceOrderNos.join('、')}`;

    await this.unitOfWork.executeInTransaction(async () => {
      const tx = this.unitOfWork.getClient();

      // 9a: 查询即将作废的个人算力总量（用于账户扣减）
      const personalSumResult = await tx.contributionRecord.aggregate({
        where: {
          sourceAdoptionId: { in: portionSourceAdoptionIds },
          sourceType: 'PERSONAL',
          isExpired: false,
        },
        _sum: { amount: true },
      });
      const expiredPersonalStr = personalSumResult._sum.amount?.toString() ?? '0';

      // 9a-team: 查询即将作废的团队算力记录（必须在 9b 过期之前查询）
      //
      // 目的：在事务内完整镜像 updateContribution 的反向操作，确保上游推荐人账户
      // 的 effectiveContribution / levelXPending / bonusTierXPending 精确回滚，
      // 避免新树算力 (9d) 再次叠加后造成双倍计入。
      const oldTeamRecords = await tx.contributionRecord.findMany({
        where: {
          sourceAdoptionId: { in: portionSourceAdoptionIds },
          sourceType: { in: ['TEAM_LEVEL', 'TEAM_BONUS'] },
          isExpired: false,
        },
        select: {
          accountSequence: true,
          sourceType: true,
          levelDepth: true,
          bonusTier: true,
          amount: true,
        },
      });

      // 9b: 作废旧份额算力记录（全部类型：PERSONAL + TEAM_LEVEL + TEAM_BONUS）
      const expiredCount = await tx.contributionRecord.updateMany({
        where: {
          sourceAdoptionId: { in: portionSourceAdoptionIds },
          isExpired: false,
        },
        data: {
          isExpired: true,
          expiredAt: miningEnabledAt,
          remark: expireRemark,
        },
      });

      this.logger.log(
        `[PRE-PLANTING-MERGE] Expired ${expiredCount.count} portion records for merge ${mergeNo}`,
      );

      // 9b-sync: 查询已作废的记录并发布同步事件（mining-admin-service 需要更新 isExpired 状态）
      if (expiredCount.count > 0) {
        const expiredRecords = await tx.contributionRecord.findMany({
          where: { sourceAdoptionId: { in: portionSourceAdoptionIds }, isExpired: true },
        });
        const expiredEvents = expiredRecords.map((record) => {
          const event = new ContributionRecordSyncedEvent(
            record.id,
            record.accountSequence,
            record.sourceType,
            record.sourceAdoptionId,
            record.sourceAccountSequence,
            record.treeCount,
            record.baseContribution.toString(),
            record.distributionRate.toString(),
            record.levelDepth,
            record.bonusTier,
            record.amount.toString(),
            record.effectiveDate,
            record.expireDate,
            true,
            record.createdAt,
          );
          return {
            aggregateType: ContributionRecordSyncedEvent.AGGREGATE_TYPE,
            aggregateId: record.id.toString(),
            eventType: ContributionRecordSyncedEvent.EVENT_TYPE,
            payload: event.toPayload(),
          };
        });
        await this.outboxRepository.saveMany(expiredEvents);
      }

      // 9c: 从认种人账户扣减旧个人算力（personal_contribution - 和 effective_contribution -）
      if (parseFloat(expiredPersonalStr) > 0) {
        await tx.contributionAccount.updateMany({
          where: { accountSequence },
          data: {
            personalContribution: { decrement: expiredPersonalStr },
            effectiveContribution: { decrement: expiredPersonalStr },
          },
        });
      }

      // 9c-team: 从上游推荐人账户精确扣减旧团队算力
      //
      // 逻辑：镜像 updateContribution 的 increment 操作，按账户 + 层级/加成档位聚合后
      // 在同一事务内 decrement。9d 的 saveDistributionResult 会重新以新树金额 increment，
      // 最终净效果 ≈ 0（如利率未变则恰好为 0）。
      //
      // 字段映射（与 ContributionAccountRepository.updateContribution 完全对称）：
      //   TEAM_LEVEL → level${depth}Pending, totalLevelPending, totalPending, effectiveContribution
      //   TEAM_BONUS → bonusTier${tier}Pending, totalBonusPending, totalPending, effectiveContribution
      if (oldTeamRecords.length > 0) {
        // 按账户聚合：levelDecrements / bonusDecrements
        const decrementsByAccount = new Map<string, {
          levelDecrements: Map<number, Decimal>;
          bonusDecrements: Map<number, Decimal>;
        }>();

        for (const record of oldTeamRecords) {
          const seq = record.accountSequence;
          if (!decrementsByAccount.has(seq)) {
            decrementsByAccount.set(seq, {
              levelDecrements: new Map(),
              bonusDecrements: new Map(),
            });
          }
          const entry = decrementsByAccount.get(seq)!;
          const amt = new Decimal(record.amount.toString());

          if (record.sourceType === 'TEAM_LEVEL' && record.levelDepth != null) {
            const prev = entry.levelDecrements.get(record.levelDepth) ?? new Decimal(0);
            entry.levelDecrements.set(record.levelDepth, prev.plus(amt));
          } else if (record.sourceType === 'TEAM_BONUS' && record.bonusTier != null) {
            const prev = entry.bonusDecrements.get(record.bonusTier) ?? new Decimal(0);
            entry.bonusDecrements.set(record.bonusTier, prev.plus(amt));
          }
        }

        // 每个上游账户一次 update，避免多次 round-trip
        for (const [seq, { levelDecrements, bonusDecrements }] of decrementsByAccount) {
          const totalLevel = [...levelDecrements.values()].reduce(
            (a, b) => a.plus(b), new Decimal(0),
          );
          const totalBonus = [...bonusDecrements.values()].reduce(
            (a, b) => a.plus(b), new Decimal(0),
          );
          const grandTotal = totalLevel.plus(totalBonus);
          if (grandTotal.lte(0)) continue;

          // 动态构建 updateData（与 updateContribution 的 increment 完全对称的 decrement）
          // 字段名（level${depth}Pending 等）在运行时才确定，无法满足 Prisma 的严格输入类型，
          // 与 ContributionAccountRepository.updateContribution 的 [levelPendingField] 同理。
          const updateData: Record<string, any> = {
            effectiveContribution: { decrement: grandTotal.toString() },
            totalPending: { decrement: grandTotal.toString() },
          };
          for (const [depth, amt] of levelDecrements) {
            updateData[`level${depth}Pending`] = { decrement: amt.toString() };
          }
          if (totalLevel.gt(0)) {
            updateData.totalLevelPending = { decrement: totalLevel.toString() };
          }
          for (const [tier, amt] of bonusDecrements) {
            updateData[`bonusTier${tier}Pending`] = { decrement: amt.toString() };
          }
          if (totalBonus.gt(0)) {
            updateData.totalBonusPending = { decrement: totalBonus.toString() };
          }

          await tx.contributionAccount.updateMany({
            where: { accountSequence: seq },
            data: updateData as any, // dynamic field names cannot satisfy Prisma's strict input type
          });

          this.logger.log(
            `[PRE-PLANTING-MERGE] Decremented upstream team contribution: seq=${seq}, ` +
            `level=${totalLevel.toString()}, bonus=${totalBonus.toString()}`,
          );
        }
      }

      // 9d: 创建新树算力分配记录（personal 70% + 团队15级 7.5% + 加成奖励 7.5%）
      //     saveDistributionResult 内部的 publishUpdatedAccountEvents 会在事务提交后
      //     通过 Outbox 发布 ContributionAccountUpdatedEvent，此时账户值已正确（净效果≈0），
      //     mining-service 消费后 totalContribution 保持不变。
      await this.saveDistributionResult(newTreeResult, mergeSourceAdoptionId, accountSequence);

      // 9e: 为新树算力记录补充 remark（标注合成来源）
      await tx.contributionRecord.updateMany({
        where: { sourceAdoptionId: mergeSourceAdoptionId },
        data: { remark: mergeRemark },
      });

      // 9f: 在 synced_adoptions 中写入合并树记录，供 sell-restriction 检查（has_real_tree 判断）
      // originalAdoptionId 使用 20B 偏移，与 SellRestrictionService.MERGE_OFFSET 一致
      const totalPortionCount = sourceOrders.reduce((sum, o) => sum + o.portionCount, 0);
      await tx.syncedAdoption.upsert({
        where: { originalAdoptionId: mergeSourceAdoptionId },
        create: {
          originalAdoptionId: mergeSourceAdoptionId,
          accountSequence,
          treeCount: 1,
          adoptionDate: miningEnabledAt,
          status: 'MINING_ENABLED',
          selectedProvince: firstOrder.provinceCode ?? '',
          selectedCity: firstOrder.cityCode ?? '',
          contributionPerTree: contributionPerTree,
          sourceSequenceNum: BigInt(0),
          distributionSummary: `PRE_PLANTING_MERGE:${mergeNo}:portions=${totalPortionCount}`,
        },
        update: {}, // 幂等：已存在则不修改
      });
    });

    // Step 10: 插入幂等标记（最终一致性，事务提交后 best-effort 写入）
    try {
      await this.prePlantingPrisma.prePlantingSyncedMerge.create({
        data: {
          mergeNo,
          accountSequence,
          sourceOrderNos,
          newSourceAdoptionId: mergeSourceAdoptionId,
          sourceTopic: result.sourceTopic,
          sourceOffset: result.sourceOffset,
        },
      });
    } catch (error: any) {
      if (error.code === 'P2002') {
        this.logger.debug(`[PRE-PLANTING-MERGE] Idempotency marker already exists: ${mergeNo}`);
      } else {
        this.logger.error(`[PRE-PLANTING-MERGE] Failed to insert idempotency marker: ${mergeNo}`, error);
      }
    }

    this.logger.log(
      `[PRE-PLANTING-MERGE] Swap completed: mergeNo=${mergeNo}, ` +
      `accountSequence=${accountSequence}, ` +
      `expiredPortions=${portionSourceAdoptionIds.length}, ` +
      `newSourceAdoptionId=${mergeSourceAdoptionId}`,
    );
  }

  private async publishContributionAccountUpdatedEvent(
    account: ContributionAccountAggregate,
  ): Promise<void> {
    const totalContribution = account.personalContribution.value
      .plus(account.totalLevelPending.value)
      .plus(account.totalBonusPending.value);

    const event = new ContributionAccountUpdatedEvent(
      account.accountSequence,
      account.personalContribution.value.toString(),
      account.totalLevelPending.value.toString(),
      account.totalBonusPending.value.toString(),
      totalContribution.toString(),
      account.effectiveContribution.value.toString(),
      account.hasAdopted,
      account.directReferralAdoptedCount,
      account.unlockedLevelDepth,
      account.unlockedBonusTiers,
      account.createdAt,
    );

    await this.outboxRepository.save({
      aggregateType: ContributionAccountUpdatedEvent.AGGREGATE_TYPE,
      aggregateId: account.accountSequence,
      eventType: ContributionAccountUpdatedEvent.EVENT_TYPE,
      payload: event.toPayload(),
    });
  }
}
