import { Injectable, Logger, ConflictException, NotFoundException, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service';
import { MiningAccountRepository } from '../../infrastructure/persistence/repositories/mining-account.repository';
import { MiningConfigRepository } from '../../infrastructure/persistence/repositories/mining-config.repository';
import { OutboxRepository } from '../../infrastructure/persistence/repositories/outbox.repository';
import { ShareAmount } from '../../domain/value-objects/share-amount.vo';
import Decimal from 'decimal.js';

export interface ManualMiningCalculateRequest {
  accountSequence: string;
  adoptionDate: string; // ISO date string
}

export interface ManualMiningCalculateResult {
  accountSequence: string;
  adoptionDate: string;
  effectiveDate: string;
  totalSeconds: string;
  userContribution: string;
  networkContribution: string;
  secondDistribution: string;
  contributionRatio: string;
  estimatedAmount: string;
  alreadyProcessed: boolean;
}

export interface ManualMiningExecuteRequest {
  accountSequence: string;
  adoptionDate: string; // ISO date string
  operatorId: string;
  operatorName: string;
  reason: string;
}

export interface ManualMiningExecuteResult {
  success: boolean;
  recordId: string;
  accountSequence: string;
  amount: string;
  adoptionDate: string;
  effectiveDate: string;
  totalSeconds: string;
  message: string;
}

/**
 * 手工补发挖矿服务
 */
@Injectable()
export class ManualMiningService {
  private readonly logger = new Logger(ManualMiningService.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly miningAccountRepository: MiningAccountRepository,
    private readonly miningConfigRepository: MiningConfigRepository,
    private readonly outboxRepository: OutboxRepository,
  ) {}

  /**
   * 计算预估补发金额（不执行入账）
   */
  async calculate(request: ManualMiningCalculateRequest): Promise<ManualMiningCalculateResult> {
    const { accountSequence, adoptionDate: adoptionDateStr } = request;

    // 检查是否已补发过
    const existing = await this.prisma.manualMiningRecord.findUnique({
      where: { accountSequence },
    });

    if (existing) {
      return {
        accountSequence,
        adoptionDate: adoptionDateStr,
        effectiveDate: existing.effectiveDate.toISOString().split('T')[0],
        totalSeconds: existing.totalSeconds.toString(),
        userContribution: existing.userContribution.toString(),
        networkContribution: existing.networkContribution.toString(),
        secondDistribution: existing.secondDistribution.toString(),
        contributionRatio: existing.contributionRatio.toString(),
        estimatedAmount: existing.amount.toString(),
        alreadyProcessed: true,
      };
    }

    // 获取用户挖矿账户
    const account = await this.miningAccountRepository.findByAccountSequence(accountSequence);
    if (!account) {
      throw new NotFoundException(`用户挖矿账户不存在: ${accountSequence}`);
    }

    if (account.totalContribution.isZero()) {
      throw new BadRequestException('用户算力为零，无法补发');
    }

    // 获取挖矿配置
    const config = await this.miningConfigRepository.getConfig();
    if (!config) {
      throw new BadRequestException('挖矿配置不存在');
    }
    if (config.networkTotalContribution.isZero()) {
      throw new BadRequestException('全网理论算力为零');
    }

    // 计算时间参数
    const adoptionDate = new Date(adoptionDateStr);
    const effectiveDate = this.getNextDay(adoptionDate);
    const now = new Date();
    const totalSeconds = BigInt(Math.floor((now.getTime() - effectiveDate.getTime()) / 1000));

    if (totalSeconds <= 0n) {
      throw new BadRequestException('认种日期必须早于今天');
    }

    // 计算补发金额
    const ratio = account.totalContribution.value.dividedBy(config.networkTotalContribution.value);
    const amount = config.secondDistribution.value
      .times(totalSeconds.toString())
      .times(ratio);

    return {
      accountSequence,
      adoptionDate: adoptionDateStr,
      effectiveDate: effectiveDate.toISOString().split('T')[0],
      totalSeconds: totalSeconds.toString(),
      userContribution: account.totalContribution.value.toString(),
      networkContribution: config.networkTotalContribution.value.toString(),
      secondDistribution: config.secondDistribution.value.toString(),
      contributionRatio: ratio.toString(),
      estimatedAmount: amount.toFixed(8),
      alreadyProcessed: false,
    };
  }

  /**
   * 执行手工补发
   */
  async execute(request: ManualMiningExecuteRequest): Promise<ManualMiningExecuteResult> {
    const { accountSequence, adoptionDate: adoptionDateStr, operatorId, operatorName, reason } = request;

    // 校验必填字段
    if (!reason || reason.trim().length === 0) {
      throw new BadRequestException('补发原因不能为空');
    }

    // 检查是否已补发过
    const existing = await this.prisma.manualMiningRecord.findUnique({
      where: { accountSequence },
    });

    if (existing) {
      throw new ConflictException(`该用户已于 ${existing.createdAt.toISOString()} 补发过挖矿收益，操作人: ${existing.operatorName}`);
    }

    // 获取用户挖矿账户
    const account = await this.miningAccountRepository.findByAccountSequence(accountSequence);
    if (!account) {
      throw new NotFoundException(`用户挖矿账户不存在: ${accountSequence}`);
    }

    if (account.totalContribution.isZero()) {
      throw new BadRequestException('用户算力为零，无法补发');
    }

    // 获取挖矿配置
    const config = await this.miningConfigRepository.getConfig();
    if (!config) {
      throw new BadRequestException('挖矿配置不存在');
    }
    if (config.networkTotalContribution.isZero()) {
      throw new BadRequestException('全网理论算力为零');
    }

    // 计算时间参数
    const adoptionDate = new Date(adoptionDateStr);
    const effectiveDate = this.getNextDay(adoptionDate);
    const now = new Date();
    const totalSeconds = BigInt(Math.floor((now.getTime() - effectiveDate.getTime()) / 1000));

    if (totalSeconds <= 0n) {
      throw new BadRequestException('认种日期必须早于今天');
    }

    // 计算补发金额
    const ratio = account.totalContribution.value.dividedBy(config.networkTotalContribution.value);
    const amount = config.secondDistribution.value
      .times(totalSeconds.toString())
      .times(ratio);

    const manualAmount = new ShareAmount(amount);
    const description = `手工补发挖矿收益 - 认种日期:${adoptionDateStr} - 补发秒数:${totalSeconds} - 操作人:${operatorName} - 原因:${reason}`;

    // 事务执行
    const result = await this.prisma.$transaction(async (tx) => {
      // 1. 创建手工补发记录（同时起到防重复作用）
      const record = await tx.manualMiningRecord.create({
        data: {
          accountSequence,
          adoptionDate,
          effectiveDate,
          executeDate: now,
          totalSeconds,
          userContribution: account.totalContribution.value,
          networkContribution: config.networkTotalContribution.value,
          secondDistribution: config.secondDistribution.value,
          contributionRatio: ratio,
          amount: manualAmount.value,
          operatorId,
          operatorName,
          reason,
        },
      });

      // 2. 更新挖矿账户余额
      const balanceBefore = account.availableBalance.value;
      const balanceAfter = balanceBefore.plus(manualAmount.value);
      const totalMinedAfter = account.totalMined.value.plus(manualAmount.value);

      await tx.miningAccount.update({
        where: { accountSequence },
        data: {
          totalMined: totalMinedAfter,
          availableBalance: balanceAfter,
          updatedAt: now,
        },
      });

      // 3. 插入交易流水
      await tx.miningTransaction.create({
        data: {
          accountSequence,
          type: 'MANUAL_MINING',
          amount: manualAmount.value,
          balanceBefore,
          balanceAfter,
          referenceId: record.id,
          referenceType: 'MANUAL_MINING',
          memo: description,
        },
      });

      // 4. 发布事件到 Kafka（供 mining-wallet-service 消费）
      await tx.outboxEvent.create({
        data: {
          aggregateType: 'ManualMining',
          aggregateId: record.id,
          eventType: 'MANUAL_MINING_COMPLETED',
          topic: 'mining.manual-mining.completed',
          key: accountSequence,
          payload: {
            eventId: record.id,
            accountSequence,
            amount: manualAmount.value.toString(),
            adoptionDate: adoptionDateStr,
            effectiveDate: effectiveDate.toISOString(),
            executeDate: now.toISOString(),
            totalSeconds: totalSeconds.toString(),
            userContribution: account.totalContribution.value.toString(),
            networkContribution: config.networkTotalContribution.value.toString(),
            secondDistribution: config.secondDistribution.value.toString(),
            contributionRatio: ratio.toString(),
            operatorId,
            operatorName,
            reason,
          },
          status: 'PENDING',
        },
      });

      return record;
    });

    this.logger.log(
      `Manual mining executed: account=${accountSequence}, amount=${manualAmount.value.toFixed(8)}, operator=${operatorName}`,
    );

    return {
      success: true,
      recordId: result.id,
      accountSequence,
      amount: manualAmount.value.toString(),
      adoptionDate: adoptionDateStr,
      effectiveDate: effectiveDate.toISOString().split('T')[0],
      totalSeconds: totalSeconds.toString(),
      message: `成功补发 ${manualAmount.value.toFixed(8)} 积分股`,
    };
  }

  /**
   * 查询手工补发记录列表
   */
  async listRecords(page: number = 1, pageSize: number = 20): Promise<{
    items: any[];
    total: number;
    page: number;
    pageSize: number;
    totalPages: number;
  }> {
    const [items, total] = await Promise.all([
      this.prisma.manualMiningRecord.findMany({
        orderBy: { createdAt: 'desc' },
        skip: (page - 1) * pageSize,
        take: pageSize,
      }),
      this.prisma.manualMiningRecord.count(),
    ]);

    return {
      items: items.map((item) => ({
        id: item.id,
        accountSequence: item.accountSequence,
        adoptionDate: item.adoptionDate.toISOString().split('T')[0],
        effectiveDate: item.effectiveDate.toISOString().split('T')[0],
        executeDate: item.executeDate.toISOString(),
        totalSeconds: item.totalSeconds.toString(),
        amount: item.amount.toString(),
        operatorId: item.operatorId,
        operatorName: item.operatorName,
        reason: item.reason,
        walletSynced: item.walletSynced,
        walletSyncedAt: item.walletSyncedAt?.toISOString() || null,
        createdAt: item.createdAt.toISOString(),
      })),
      total,
      page,
      pageSize,
      totalPages: Math.ceil(total / pageSize),
    };
  }

  /**
   * 根据 accountSequence 查询补发记录
   */
  async getRecordByAccountSequence(accountSequence: string): Promise<any | null> {
    const record = await this.prisma.manualMiningRecord.findUnique({
      where: { accountSequence },
    });

    if (!record) {
      return null;
    }

    return {
      id: record.id,
      accountSequence: record.accountSequence,
      adoptionDate: record.adoptionDate.toISOString().split('T')[0],
      effectiveDate: record.effectiveDate.toISOString().split('T')[0],
      executeDate: record.executeDate.toISOString(),
      totalSeconds: record.totalSeconds.toString(),
      userContribution: record.userContribution.toString(),
      networkContribution: record.networkContribution.toString(),
      secondDistribution: record.secondDistribution.toString(),
      contributionRatio: record.contributionRatio.toString(),
      amount: record.amount.toString(),
      operatorId: record.operatorId,
      operatorName: record.operatorName,
      reason: record.reason,
      walletSynced: record.walletSynced,
      walletSyncedAt: record.walletSyncedAt?.toISOString() || null,
      createdAt: record.createdAt.toISOString(),
    };
  }

  /**
   * 标记钱包已同步
   */
  async markWalletSynced(recordId: string): Promise<void> {
    await this.prisma.manualMiningRecord.update({
      where: { id: recordId },
      data: {
        walletSynced: true,
        walletSyncedAt: new Date(),
      },
    });
  }

  /**
   * 获取次日日期
   */
  private getNextDay(date: Date): Date {
    const next = new Date(date);
    next.setDate(next.getDate() + 1);
    next.setHours(0, 0, 0, 0);
    return next;
  }
}
