import { Injectable, Logger, BadRequestException, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../../../infrastructure/persistence/prisma/prisma.service';
import { OutboxRepository, OutboxEventData } from '../../../infrastructure/persistence/repositories/outbox.repository';
import { WalletServiceClient } from '../../../infrastructure/external/wallet-service.client';
import { PrePlantingOrder } from '../../domain/aggregates/pre-planting-order.aggregate';
import { PrePlantingMerge } from '../../domain/aggregates/pre-planting-merge.aggregate';
import { PrePlantingOrderStatus } from '../../domain/value-objects/pre-planting-order-status.enum';
import {
  PRE_PLANTING_PRICE_PER_PORTION,
  PRE_PLANTING_PORTIONS_PER_TREE,
} from '../../domain/value-objects/pre-planting-right-amounts';
import { PrePlantingOrderRepository } from '../../infrastructure/repositories/pre-planting-order.repository';
import { PrePlantingPositionRepository } from '../../infrastructure/repositories/pre-planting-position.repository';
import { PrePlantingMergeRepository } from '../../infrastructure/repositories/pre-planting-merge.repository';
import { PrePlantingRewardService } from './pre-planting-reward.service';
import { PrePlantingAdminClient } from '../../infrastructure/external/pre-planting-admin.client';
import { TreePricingAdminClient } from '../../../infrastructure/external/tree-pricing-admin.client';
import { EventPublisherService } from '../../../infrastructure/kafka/event-publisher.service';
import { PdfGeneratorService } from '../../../infrastructure/pdf/pdf-generator.service';
import { IdentityServiceClient } from '../../../infrastructure/external/identity-service.client';

@Injectable()
export class PrePlantingApplicationService {
  private readonly logger = new Logger(PrePlantingApplicationService.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly outboxRepo: OutboxRepository,
    private readonly walletClient: WalletServiceClient,
    private readonly orderRepo: PrePlantingOrderRepository,
    private readonly positionRepo: PrePlantingPositionRepository,
    private readonly mergeRepo: PrePlantingMergeRepository,
    private readonly rewardService: PrePlantingRewardService,
    private readonly adminClient: PrePlantingAdminClient,
    private readonly treePricingClient: TreePricingAdminClient,
    private readonly eventPublisher: EventPublisherService,
    private readonly pdfGeneratorService: PdfGeneratorService,
    private readonly identityServiceClient: IdentityServiceClient,
  ) {}

  /**
   * 购买预种份额
   *
   * Flow: 校验 → 冻结余额 → 事务(创建订单+更新持仓+分配记录+outbox) → 确认扣款+分配 → 检查合并
   */
  async purchasePortion(
    userId: bigint,
    accountSequence: string,
    portionCount: number,
    provinceCode: string,
    cityCode: string,
    provinceName?: string,
    cityName?: string,
  ): Promise<{ orderNo: string; merged: boolean; mergeNo?: string }> {
    this.logger.log(
      `[PRE-PLANTING] Purchase request: userId=${userId}, portions=${portionCount}, ` +
      `province=${provinceCode}, city=${cityCode}`,
    );

    // Step 1: 前置校验
    await this.validatePurchase(userId, portionCount);

    // 获取当前定价配置（含总部运营成本压力涨价）
    const pricingConfig = await this.treePricingClient.getTreePricingConfig();
    // 预种每份加价 = Math.floor(每棵树加价 / 份数)，涨价部分全额归总部 (S0000000001)
    const portionSupplement = Math.floor(pricingConfig.currentSupplement / PRE_PLANTING_PORTIONS_PER_TREE);

    const orderNo = this.generateOrderNo();
    const totalAmount = portionCount * (PRE_PLANTING_PRICE_PER_PORTION + portionSupplement);

    // Step 2: 冻结余额
    await this.walletClient.freezeForPlanting({
      userId: userId.toString(),
      accountSequence,
      amount: totalAmount,
      orderId: orderNo,
    });

    let merged = false;
    let mergeNo: string | undefined;
    let rewardAllocations: import('./pre-planting-reward.service').RewardAllocation[] = [];

    try {
      // Step 3-4: 事务内处理（创建订单 + 更新持仓 + 分配记录 + outbox）
      // 注意：事务内只做 DB 写入，不做 HTTP 调用
      await this.prisma.$transaction(async (tx) => {
        // 创建预种订单（portionSupplement 快照到订单，确保后续涨价不影响已创建订单）
        const order = PrePlantingOrder.create(
          orderNo,
          userId,
          accountSequence,
          portionCount,
          provinceCode,
          cityCode,
          portionSupplement,
        );

        // 获取或创建持仓
        const position = await this.positionRepo.getOrCreate(tx, userId, accountSequence);

        // 续购时验证省市一致性
        if (position.provinceCode && position.provinceCode !== provinceCode) {
          throw new BadRequestException('续购必须与首次购买选择相同省份');
        }

        // 增加份数
        position.addPortions(portionCount, provinceCode, cityCode, provinceName, cityName);

        // 标记订单为已支付
        order.markAsPaid(position.totalPortions, position.availablePortions);

        // 持久化
        await this.orderRepo.save(tx, order);
        await this.positionRepo.save(tx, position);

        // 分配 10 类权益 + 涨价补充 — 事务内只持久化记录，返回 allocations 供事务后转账
        rewardAllocations = await this.rewardService.prepareAndPersistRewards(
          tx,
          orderNo,
          accountSequence,
          provinceCode,
          cityCode,
          portionCount,
          portionSupplement,
        );

        // Outbox: 购买事件（包装为 { eventName, data } 格式，与现有 planting 事件一致）
        const outboxEvents: OutboxEventData[] = order.domainEvents.map((event) => ({
          eventType: event.type,
          topic: 'pre-planting.portion.purchased',
          key: accountSequence,
          payload: {
            eventName: 'pre-planting.portion.purchased',
            data: event.data,
          } as Record<string, unknown>,
          aggregateId: event.aggregateId,
          aggregateType: event.aggregateType,
        }));

        // Step 6: 检查是否触发合并（循环处理，支持一次购买多份触发多次合并）
        const mergeResults: { mergeNo: string; domainEvents: typeof order.domainEvents }[] = [];
        while (position.canMerge()) {
          const mergeResult = await this.performMerge(tx, userId, accountSequence, position);
          mergeResults.push(mergeResult);

          // 合并事件也写入 Outbox
          for (const event of mergeResult.domainEvents) {
            outboxEvents.push({
              eventType: event.type,
              topic: 'pre-planting.merged',
              key: accountSequence,
              payload: {
                eventName: 'pre-planting.merged',
                data: event.data,
              } as Record<string, unknown>,
              aggregateId: event.aggregateId,
              aggregateType: event.aggregateType,
            });
          }
        }

        if (mergeResults.length > 0) {
          merged = true;
          mergeNo = mergeResults[mergeResults.length - 1].mergeNo;
        }

        await this.outboxRepo.saveInTransaction(tx, outboxEvents);
      });

      // Step 5: 事务成功后，执行资金转账（HTTP 调用，不在事务内）
      await this.walletClient.confirmPlantingDeduction({
        userId: userId.toString(),
        accountSequence,
        orderId: orderNo,
      });

      await this.rewardService.executeAllocations(
        orderNo,
        rewardAllocations,
        accountSequence,
        portionCount,
        provinceCode,
        cityCode,
      );

      // [2026-03-01] 预种购买后触发待领取→可结算转换
      // 将用户标记为 hasPlanted=true，并结算所有 PENDING 奖励
      // 幂等：重复调用安全。失败不阻塞购买流程（奖励仍在 PENDING，不会丢失）
      try {
        const settleResult = await this.walletClient.settleAfterPrePlanting(accountSequence);
        this.logger.log(
          `[PRE-PLANTING] Settle after purchase: order=${orderNo}, ` +
          `markedAsPlanted=${settleResult.markedAsPlanted}, ` +
          `settled=${settleResult.settledCount} rewards, usdt=${settleResult.totalUsdt}`,
        );
      } catch (settleError) {
        // 结算失败不影响购买结果，奖励仍在 PENDING 状态，用户可后续手动领取
        this.logger.error(
          `[PRE-PLANTING] Failed to settle after purchase for order ${orderNo}, ` +
          `accountSequence=${accountSequence}. Rewards remain in PENDING status.`,
          settleError,
        );
      }
    } catch (error) {
      // 事务失败，解冻余额
      this.logger.error(
        `[PRE-PLANTING] Purchase failed for order ${orderNo}, unfreezing`,
        error,
      );
      await this.walletClient.unfreezeForPlanting({
        userId: userId.toString(),
        accountSequence,
        orderId: orderNo,
      }).catch((unfreezeErr) => {
        this.logger.error(
          `[PRE-PLANTING] Failed to unfreeze for order ${orderNo}`,
          unfreezeErr,
        );
      });
      throw error;
    }

    this.logger.log(
      `[PRE-PLANTING] Purchase completed: order=${orderNo}, merged=${merged}${mergeNo ? `, mergeNo=${mergeNo}` : ''}`,
    );

    return { orderNo, merged, mergeNo };
  }

  /**
   * 签署合并后的合同
   */
  async signContract(
    userId: bigint,
    mergeNo: string,
  ): Promise<void> {
    this.logger.log(`[PRE-PLANTING] Sign contract: userId=${userId}, mergeNo=${mergeNo}`);

    // 事务内签约，返回合并记录数据供后续事件发布
    const mergeData = await this.prisma.$transaction(async (tx) => {
      const merge = await this.mergeRepo.findByMergeNo(tx, mergeNo);
      if (!merge) {
        throw new NotFoundException(`合并记录 ${mergeNo} 不存在`);
      }
      if (merge.userId !== userId) {
        throw new BadRequestException('无权操作此合并记录');
      }

      merge.signContract();
      await this.mergeRepo.save(tx, merge);

      // Outbox: 合同签署事件
      const outboxEvents: OutboxEventData[] = merge.domainEvents.map((event) => ({
        eventType: event.type,
        topic: 'pre-planting.contract.signed',
        key: merge.accountSequence,
        payload: {
          eventName: 'pre-planting.contract.signed',
          data: event.data,
        } as Record<string, unknown>,
        aggregateId: event.aggregateId,
        aggregateType: event.aggregateType,
      }));
      await this.outboxRepo.saveInTransaction(tx, outboxEvents);

      return {
        mergeNo: merge.mergeNo,
        accountSequence: merge.accountSequence,
        userId: merge.userId.toString(),
        treeCount: merge.treeCount,
      };
    });

    // 事务成功后，发布 PlantingOrderPaid 到 planting-events topic
    // wallet-service 消费此事件触发：
    // 1. markUserAsPlanted(accountSequence) — 设置 hasPlanted=true
    // 2. settleUserPendingRewards(accountSequence) — 结算所有 PENDING 奖励
    try {
      await this.eventPublisher.publishRawToPlantingEvents(
        mergeData.mergeNo,
        'PlantingOrderPaid',
        {
          orderNo: mergeData.mergeNo,
          accountSequence: mergeData.accountSequence,
          userId: mergeData.userId,
          treeCount: mergeData.treeCount,
        },
      );
      this.logger.log(
        `[PRE-PLANTING] PlantingOrderPaid event published for merge ${mergeNo}, ` +
        `accountSequence=${mergeData.accountSequence}`,
      );
    } catch (error) {
      // 发布失败不阻塞签约流程，后续可通过补偿机制重试
      this.logger.error(
        `[PRE-PLANTING] Failed to publish PlantingOrderPaid for merge ${mergeNo}`,
        error,
      );
    }

    this.logger.log(`[PRE-PLANTING] Contract signed: mergeNo=${mergeNo}`);
  }

  /**
   * 获取用户预种持仓信息
   */
  async getPosition(userId: bigint): Promise<{
    totalPortions: number;
    availablePortions: number;
    mergedPortions: number;
    totalTreesMerged: number;
    provinceCode: string | null;
    provinceName: string | null;
    cityCode: string | null;
    cityName: string | null;
    firstPurchaseAt: Date | null;
  } | null> {
    return this.prisma.$transaction(async (tx) => {
      const position = await this.positionRepo.findByUserId(tx, userId);
      if (!position) return null;
      return {
        totalPortions: position.totalPortions,
        availablePortions: position.availablePortions,
        mergedPortions: position.mergedPortions,
        totalTreesMerged: position.totalTreesMerged,
        provinceCode: position.provinceCode,
        provinceName: position.provinceName,
        cityCode: position.cityCode,
        cityName: position.cityName,
        firstPurchaseAt: position.firstPurchaseAt,
      };
    });
  }

  /**
   * 获取用户预种订单列表
   */
  async getOrders(userId: bigint): Promise<{
    orderNo: string;
    portionCount: number;
    totalAmount: number;
    provinceCode: string;
    cityCode: string;
    status: string;
    createdAt: Date;
    paidAt: Date | null;
    mergedAt: Date | null;
  }[]> {
    return this.prisma.$transaction(async (tx) => {
      const orders = await this.orderRepo.findByUserId(tx, userId);
      return orders.map((o) => ({
        orderNo: o.orderNo,
        portionCount: o.portionCount,
        totalAmount: o.totalAmount,
        provinceCode: o.provinceCode,
        cityCode: o.cityCode,
        status: o.status,
        createdAt: o.createdAt,
        paidAt: o.paidAt,
        mergedAt: o.mergedAt,
      }));
    });
  }

  /**
   * 获取用户合并记录列表
   */
  async getMerges(userId: bigint): Promise<{
    mergeNo: string;
    sourceOrderNos: string[];
    totalPortions: number;
    treeCount: number;
    contractStatus: string;
    contractSignedAt: Date | null;
    miningEnabledAt: Date | null;
    mergedAt: Date;
  }[]> {
    return this.prisma.$transaction(async (tx) => {
      const merges = await this.mergeRepo.findByUserId(tx, userId);
      if (merges.length === 0) return [];

      // 批量查询所有合并的来源订单份数
      const allOrderNos = merges.flatMap((m) => m.sourceOrderNos);
      const orderRecords = await tx.prePlantingOrder.findMany({
        where: { orderNo: { in: allOrderNos } },
        select: { orderNo: true, portionCount: true },
      });
      const portionByOrderNo = new Map(orderRecords.map((o) => [o.orderNo, o.portionCount]));

      return merges.map((m) => ({
        mergeNo: m.mergeNo,
        sourceOrderNos: m.sourceOrderNos,
        totalPortions: m.sourceOrderNos.reduce((sum, no) => sum + (portionByOrderNo.get(no) ?? 1), 0),
        treeCount: m.treeCount,
        contractStatus: m.contractStatus,
        contractSignedAt: m.contractSignedAt,
        miningEnabledAt: m.miningEnabledAt,
        mergedAt: m.mergedAt,
      }));
    });
  }

  /**
   * 获取单条合并记录详情
   */
  async getMergeDetail(
    userId: bigint,
    mergeNo: string,
  ): Promise<{
    mergeNo: string;
    sourceOrderNos: string[];
    sourceOrders: { orderNo: string; portionCount: number; totalAmount: number }[];
    totalPortions: number;
    totalAmount: number;
    treeCount: number;
    contractStatus: string;
    contractSignedAt: Date | null;
    miningEnabledAt: Date | null;
    mergedAt: Date;
  }> {
    return this.prisma.$transaction(async (tx) => {
      const merge = await this.mergeRepo.findByMergeNo(tx, mergeNo);
      if (!merge || merge.userId !== userId) {
        throw new NotFoundException(`合并记录不存在: ${mergeNo}`);
      }

      // 查询源订单的份数和金额
      const orderRecords = await tx.prePlantingOrder.findMany({
        where: { orderNo: { in: merge.sourceOrderNos } },
        select: { orderNo: true, portionCount: true, totalAmount: true },
        orderBy: { createdAt: 'asc' },
      });

      const sourceOrders = orderRecords.map((o) => ({
        orderNo: o.orderNo,
        portionCount: o.portionCount,
        totalAmount: Number(o.totalAmount),
      }));
      const totalPortions = sourceOrders.reduce((sum, o) => sum + o.portionCount, 0);
      const totalAmount = sourceOrders.reduce((sum, o) => sum + o.totalAmount, 0);

      return {
        mergeNo: merge.mergeNo,
        sourceOrderNos: merge.sourceOrderNos,
        sourceOrders,
        totalPortions,
        totalAmount,
        treeCount: merge.treeCount,
        contractStatus: merge.contractStatus,
        contractSignedAt: merge.contractSignedAt,
        miningEnabledAt: merge.miningEnabledAt,
        mergedAt: merge.mergedAt,
      };
    });
  }

  /**
   * 生成合并合同预览 PDF（用于前端展示）
   */
  async getMergeContractPdf(userId: bigint, mergeNo: string): Promise<Buffer> {
    const merge = await this.prisma.$transaction(async (tx) => {
      const m = await this.mergeRepo.findByMergeNo(tx, mergeNo);
      if (!m || m.userId !== userId) {
        throw new NotFoundException(`合并记录不存在: ${mergeNo}`);
      }
      return m;
    });

    // 获取用户 KYC 信息 - 未完成实名认证则不允许查看合同
    const kycInfo = await this.identityServiceClient.getUserKycInfo(merge.accountSequence);
    if (!kycInfo || !kycInfo.realName) {
      throw new BadRequestException('请先完成实名认证方可查看和签署合同');
    }

    // 从源订单汇总实际支付的绿积分金额
    const sourceOrders = await this.prisma.prePlantingOrder.findMany({
      where: { orderNo: { in: merge.sourceOrderNos } },
      select: { totalAmount: true },
    });
    const greenPointsAmount = sourceOrders.reduce((sum, o) => sum + Number(o.totalAmount), 0);
    const totalAmount = Math.round(greenPointsAmount * 1.1 * 100) / 100; // 含税 CNY

    // 生成北京时间日期
    const now = new Date(Date.now() + 8 * 60 * 60 * 1000);
    const signingDate = `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, '0')}-${String(now.getUTCDate()).padStart(2, '0')}`;

    return this.pdfGeneratorService.generateContractPdf({
      contractNo: mergeNo,
      userRealName: kycInfo.realName,
      userIdCard: kycInfo.idCardNumber || '',
      userPhone: kycInfo.phoneNumber || '',
      treeCount: merge.treeCount,
      totalAmount,
      greenPointsAmount,
      signingDate,
    });
  }

  /**
   * 获取预种资格信息（供内部 API + 移动端购买页使用）
   *
   * 返回字段同时满足两个消费者：
   * - 内部 API: hasPrePlanting, canApplyAuthorization, canTrade
   * - 移动端购买页: canPurchase, maxAdditional, message
   */
  async getEligibility(accountSequence: string): Promise<{
    hasPrePlanting: boolean;
    totalPortions: number;
    totalTreesMerged: number;
    canApplyAuthorization: boolean;
    canTrade: boolean;
    canPurchase: boolean;
    maxAdditional: number | null;
    message: string | null;
  }> {
    const config = await this.adminClient.getPrePlantingConfig();

    return this.prisma.$transaction(async (tx) => {
      const position = await this.positionRepo.findByAccountSequence(tx, accountSequence);

      // 计算购买资格
      let canPurchase = true;
      let maxAdditional: number | null = null;
      let message: string | null = null;

      if (config.isActive) {
        // 开关打开，任何人都可以购买
        canPurchase = true;
      } else if (!position || position.totalPortions === 0) {
        // 开关关闭 + 无持仓 → 不可购买
        canPurchase = false;
        message = '预种功能待开启';
      } else {
        // 开关关闭 + 有持仓 → 看是否还能续购凑满
        const additionalNeeded = position.maxAdditionalPortionsToMerge();
        if (additionalNeeded === 0) {
          canPurchase = false;
          message = '预种功能已关闭，您当前份额已满，无法继续购买';
        } else {
          canPurchase = true;
          maxAdditional = additionalNeeded;
          message = `预种功能已暂停，您可继续购买 ${additionalNeeded} 份以凑满合并`;
        }
      }

      if (!position) {
        return {
          hasPrePlanting: false,
          totalPortions: 0,
          totalTreesMerged: 0,
          canApplyAuthorization: true,
          canTrade: true,
          canPurchase,
          maxAdditional,
          message,
        };
      }
      return {
        hasPrePlanting: true,
        totalPortions: position.totalPortions,
        totalTreesMerged: position.totalTreesMerged,
        canApplyAuthorization: position.totalTreesMerged >= 1,
        canTrade: position.totalTreesMerged >= 1,
        canPurchase,
        maxAdditional,
        message,
      };
    });
  }

  // ===== Private Methods =====

  private async validatePurchase(
    userId: bigint,
    portionCount: number,
  ): Promise<void> {
    if (portionCount < 1) {
      throw new BadRequestException('购买份数必须大于 0');
    }

    const config = await this.adminClient.getPrePlantingConfig();

    if (config.isActive) {
      return; // 开关打开，任何人都可以购买
    }

    // 开关关闭：检查续购规则
    const position = await this.prisma.$transaction(async (tx) => {
      return this.positionRepo.findByUserId(tx, userId);
    });

    if (!position || position.totalPortions === 0) {
      throw new BadRequestException('预种功能待开启');
    }

    const maxAdditional = position.maxAdditionalPortionsToMerge();
    if (maxAdditional === 0) {
      throw new BadRequestException('预种功能已关闭，您当前份额已满，无法继续购买');
    }
    if (portionCount > maxAdditional) {
      throw new BadRequestException(`当前只可再购买 ${maxAdditional} 份以凑满 ${PRE_PLANTING_PORTIONS_PER_TREE} 份`);
    }
  }

  private async performMerge(
    tx: import('@prisma/client').Prisma.TransactionClient,
    userId: bigint,
    accountSequence: string,
    position: import('../../domain/aggregates/pre-planting-position.aggregate').PrePlantingPosition,
  ) {
    // 获取所有已支付订单，按份数累加取够 PRE_PLANTING_PORTIONS_PER_TREE 份
    // 注意：一笔订单可能包含多份（portionCount > 1），所以不能按订单条数判断
    const allPaidOrders = await this.orderRepo.findPaidOrdersByUserId(tx, userId);

    let accumulatedPortions = 0;
    const sourceOrders: typeof allPaidOrders = [];
    for (const order of allPaidOrders) {
      sourceOrders.push(order);
      accumulatedPortions += order.portionCount;
      if (accumulatedPortions >= PRE_PLANTING_PORTIONS_PER_TREE) break;
    }

    if (accumulatedPortions < PRE_PLANTING_PORTIONS_PER_TREE) {
      throw new Error(
        `不足 ${PRE_PLANTING_PORTIONS_PER_TREE} 份进行合并（当前 ${accumulatedPortions} 份，来自 ${sourceOrders.length} 笔订单）`,
      );
    }

    const sourceOrderNos = sourceOrders.map((o) => o.orderNo);
    const mergeNo = this.generateMergeNo();

    // 执行持仓合并
    position.performMerge();
    await this.positionRepo.save(tx, position);

    // 创建合并记录
    const merge = PrePlantingMerge.create(
      mergeNo,
      userId,
      accountSequence,
      sourceOrderNos,
      position.provinceCode || '',
      position.cityCode || '',
      position.totalTreesMerged,
    );
    await this.mergeRepo.save(tx, merge);

    // 标记源订单为已合并
    for (const order of sourceOrders) {
      order.markAsMerged(merge.id!);
      await this.orderRepo.save(tx, order);
    }

    return {
      mergeNo,
      domainEvents: merge.domainEvents,
    };
  }

  private generateOrderNo(): string {
    const timestamp = Date.now().toString(36);
    const random = Math.random().toString(36).substring(2, 8);
    return `PPL${timestamp}${random}`.toUpperCase();
  }

  private generateMergeNo(): string {
    const timestamp = Date.now().toString(36);
    const random = Math.random().toString(36).substring(2, 8);
    return `PMG${timestamp}${random}`.toUpperCase();
  }
}
