import { Injectable, Logger } from '@nestjs/common';
import { TradingCalculatorService } from '../../domain/services/trading-calculator.service';
import { BlackHoleRepository } from '../../infrastructure/persistence/repositories/black-hole.repository';
import { CirculationPoolRepository } from '../../infrastructure/persistence/repositories/circulation-pool.repository';
import { PriceSnapshotRepository } from '../../infrastructure/persistence/repositories/price-snapshot.repository';
import { TradingConfigRepository } from '../../infrastructure/persistence/repositories/trading-config.repository';
import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service';
import { Money } from '../../domain/value-objects/money.vo';
import Decimal from 'decimal.js';

export interface PriceInfo {
  price: string;
  greenPoints: string;
  blackHoleAmount: string;
  circulationPool: string;
  effectiveDenominator: string;
  burnMultiplier: string;
  minuteBurnRate: string;
  snapshotTime: Date;
  priceChangePercent: string; // 较上线首日涨跌幅
  initialPrice: string; // 上线首日价格
}

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

  // 缓存做市商 accountSequence，避免每次查 MarketMakerConfig
  private mmAccountSequence: string | null = null;

  constructor(
    private readonly blackHoleRepository: BlackHoleRepository,
    private readonly circulationPoolRepository: CirculationPoolRepository,
    private readonly priceSnapshotRepository: PriceSnapshotRepository,
    private readonly tradingConfigRepository: TradingConfigRepository,
    private readonly prisma: PrismaService,
  ) {}

  /**
   * 获取做市商积分值余额（即价格公式中的"绿积分"）
   * 做市商的 cashBalance 就是积分股池的绿积分
   */
  private async getGreenPoints(): Promise<Money> {
    if (!this.mmAccountSequence) {
      const config = await this.prisma.marketMakerConfig.findUnique({
        where: { name: 'MAIN_MARKET_MAKER' },
      });
      this.mmAccountSequence = config?.accountSequence || null;
    }
    if (!this.mmAccountSequence) return Money.zero();

    const account = await this.prisma.tradingAccount.findUnique({
      where: { accountSequence: this.mmAccountSequence },
    });
    return account ? new Money(account.cashBalance) : Money.zero();
  }

  /**
   * 获取当前价格信息
   */
  async getCurrentPrice(): Promise<PriceInfo> {
    const [greenPoints, blackHole, circulationPool, config, firstSnapshot] = await Promise.all([
      this.getGreenPoints(),
      this.blackHoleRepository.getBlackHole(),
      this.circulationPoolRepository.getPool(),
      this.tradingConfigRepository.getConfig(),
      this.priceSnapshotRepository.getFirstSnapshot(),
    ]);
    const blackHoleAmount = blackHole?.totalBurned || Money.zero();
    const circulationPoolAmount = circulationPool?.totalShares || Money.zero();

    // 计算价格
    const price = this.calculator.calculatePrice(greenPoints, blackHoleAmount, circulationPoolAmount);

    // 计算有效分母
    const effectiveDenominator = this.calculator.calculateEffectiveDenominator(
      blackHoleAmount,
      circulationPoolAmount,
    );

    // 计算销毁倍数
    const burnMultiplier = this.calculator.calculateSellBurnMultiplier(
      blackHoleAmount,
      circulationPoolAmount,
    );

    // 获取当前每分钟销毁率
    const minuteBurnRate = config?.minuteBurnRate || Money.zero();

    // 计算较上线首日的涨跌幅
    const initialPrice = firstSnapshot?.price || price;
    let priceChangePercent = new Decimal(0);
    if (!initialPrice.isZero()) {
      priceChangePercent = price.value.minus(initialPrice.value).dividedBy(initialPrice.value).times(100);
    }

    return {
      price: price.toFixed(18),
      greenPoints: greenPoints.toFixed(8),
      blackHoleAmount: blackHoleAmount.toFixed(8),
      circulationPool: circulationPoolAmount.toFixed(8),
      effectiveDenominator: effectiveDenominator.toFixed(8),
      burnMultiplier: burnMultiplier.toFixed(18),
      minuteBurnRate: minuteBurnRate.toFixed(18),
      snapshotTime: new Date(),
      // 保留12位小数，确保每分钟销毁带来的微小涨幅在前端可见
      priceChangePercent: priceChangePercent.toFixed(12),
      initialPrice: initialPrice.toFixed(18),
    };
  }

  /**
   * 获取当前销毁倍数
   */
  async getCurrentBurnMultiplier(): Promise<Decimal> {
    const [blackHole, circulationPool] = await Promise.all([
      this.blackHoleRepository.getBlackHole(),
      this.circulationPoolRepository.getPool(),
    ]);

    const blackHoleAmount = blackHole?.totalBurned || Money.zero();
    const circulationPoolAmount = circulationPool?.totalShares || Money.zero();

    return this.calculator.calculateSellBurnMultiplier(blackHoleAmount, circulationPoolAmount);
  }

  /**
   * 计算卖出销毁量
   */
  async calculateSellBurn(sellQuantity: Money): Promise<{
    burnQuantity: Money;
    burnMultiplier: Decimal;
    effectiveQuantity: Money;
  }> {
    const burnMultiplier = await this.getCurrentBurnMultiplier();
    const burnQuantity = this.calculator.calculateSellBurnAmount(sellQuantity, burnMultiplier);
    const effectiveQuantity = new Money(sellQuantity.value.plus(burnQuantity.value));

    return {
      burnQuantity,
      burnMultiplier,
      effectiveQuantity,
    };
  }

  /**
   * 计算卖出交易额
   */
  async calculateSellAmount(sellQuantity: Money): Promise<{
    amount: Money;
    burnQuantity: Money;
    effectiveQuantity: Money;
    price: Money;
  }> {
    const priceInfo = await this.getCurrentPrice();
    const price = new Money(priceInfo.price);

    const { burnQuantity, effectiveQuantity } = await this.calculateSellBurn(sellQuantity);

    const amount = this.calculator.calculateSellAmount(sellQuantity, burnQuantity, price);

    return {
      amount,
      burnQuantity,
      effectiveQuantity,
      price,
    };
  }

  /**
   * 创建价格快照
   */
  async createSnapshot(): Promise<void> {
    try {
      const [greenPoints, blackHole, circulationPool, config] = await Promise.all([
        this.getGreenPoints(),
        this.blackHoleRepository.getBlackHole(),
        this.circulationPoolRepository.getPool(),
        this.tradingConfigRepository.getConfig(),
      ]);
      const blackHoleAmount = blackHole?.totalBurned || Money.zero();
      const circulationPoolAmount = circulationPool?.totalShares || Money.zero();

      const price = this.calculator.calculatePrice(greenPoints, blackHoleAmount, circulationPoolAmount);
      const effectiveDenominator = this.calculator.calculateEffectiveDenominator(
        blackHoleAmount,
        circulationPoolAmount,
      );
      const minuteBurnRate = config?.minuteBurnRate || Money.zero();

      const snapshotTime = new Date();
      snapshotTime.setSeconds(0, 0);

      await this.priceSnapshotRepository.createSnapshot({
        snapshotTime,
        price,
        greenPoints,
        blackHoleAmount,
        circulationPool: circulationPoolAmount,
        effectiveDenominator,
        minuteBurnRate,
      });

      this.logger.debug(`Price snapshot created: ${price.toFixed(18)}`);
    } catch (error) {
      this.logger.error('Failed to create price snapshot', error);
    }
  }

  /**
   * 获取价格历史
   */
  async getPriceHistory(
    startTime: Date,
    endTime: Date,
    limit: number = 1440,
  ): Promise<
    Array<{
      time: Date;
      price: string;
      greenPoints: string;
      blackHoleAmount: string;
      circulationPool: string;
    }>
  > {
    const snapshots = await this.priceSnapshotRepository.getPriceHistory(startTime, endTime, limit);

    return snapshots.map((s) => ({
      time: s.snapshotTime,
      price: s.price.toFixed(18),
      greenPoints: s.greenPoints.toFixed(8),
      blackHoleAmount: s.blackHoleAmount.toFixed(8),
      circulationPool: s.circulationPool.toFixed(8),
    }));
  }

  /**
   * 获取最新价格快照
   */
  async getLatestSnapshot(): Promise<PriceInfo | null> {
    const [snapshot, firstSnapshot] = await Promise.all([
      this.priceSnapshotRepository.getLatestSnapshot(),
      this.priceSnapshotRepository.getFirstSnapshot(),
    ]);
    if (!snapshot) {
      return null;
    }

    const burnMultiplier = await this.getCurrentBurnMultiplier();

    // 计算较上线首日的涨跌幅
    const initialPrice = firstSnapshot?.price || snapshot.price;
    let priceChangePercent = new Decimal(0);
    if (!initialPrice.isZero()) {
      priceChangePercent = snapshot.price.value.minus(initialPrice.value).dividedBy(initialPrice.value).times(100);
    }

    return {
      price: snapshot.price.toFixed(18),
      greenPoints: snapshot.greenPoints.toFixed(8),
      blackHoleAmount: snapshot.blackHoleAmount.toFixed(8),
      circulationPool: snapshot.circulationPool.toFixed(8),
      effectiveDenominator: snapshot.effectiveDenominator.toFixed(8),
      burnMultiplier: burnMultiplier.toFixed(18),
      minuteBurnRate: snapshot.minuteBurnRate.toFixed(18),
      snapshotTime: snapshot.snapshotTime,
      // 保留12位小数，确保每分钟销毁带来的微小涨幅在前端可见
      priceChangePercent: priceChangePercent.toFixed(12),
      initialPrice: initialPrice.toFixed(18),
    };
  }

  /**
   * 获取K线数据（OHLC格式）
   * 将价格快照按周期聚合为K线数据
   *
   * @param period K线周期: 1m, 5m, 15m, 30m, 1h, 4h, 1d, 1w, 1M, 1y
   * @param limit 返回数量
   * @param before 获取此时间之前的K线数据（用于加载更多历史）
   */
  async getKlines(
    period: string = '1h',
    limit: number = 100,
    before?: Date,
  ): Promise<
    Array<{
      time: string;
      open: string;
      high: string;
      low: string;
      close: string;
      volume: string;
    }>
  > {
    // 解析周期为分钟数
    const periodMinutes = this.parsePeriodToMinutes(period);

    // 计算时间范围
    // 如果指定了before，则获取before之前的数据；否则获取到当前时间的数据
    const endTime = before ? before : new Date();
    const startTime = new Date(endTime.getTime() - periodMinutes * limit * 60 * 1000);

    // 获取原始快照数据
    const snapshots = await this.priceSnapshotRepository.getPriceHistory(
      startTime,
      endTime,
      periodMinutes * limit, // 获取足够多的数据点
    );

    if (snapshots.length === 0) {
      return [];
    }

    // 按周期聚合为K线
    const klines = this.aggregateToKlines(snapshots, periodMinutes, period);

    // 返回最近的limit条
    return klines.slice(-limit);
  }

  /**
   * 解析周期字符串为分钟数
   * 对于日历周期（1w/1M/1y），返回的是近似值，仅用于计算查询的时间范围；
   * 实际的 K 线分组由 getPeriodStart() 按日历边界对齐。
   */
  private parsePeriodToMinutes(period: string): number {
    const periodMap: Record<string, number> = {
      '1m': 1,
      '5m': 5,
      '15m': 15,
      '30m': 30,
      '1h': 60,
      '4h': 240,
      '1d': 1440,       // 1天 = 24h
      '1w': 10080,      // 1周 ≈ 7天（近似值，实际按周一对齐）
      '1M': 43200,      // 1月 ≈ 30天（近似值，实际按自然月对齐）
      '1y': 525600,     // 1年 ≈ 365天（近似值，实际按自然年对齐）
    };
    return periodMap[period] || 60;
  }

  /**
   * 将快照数据聚合为K线
   */
  private aggregateToKlines(
    snapshots: Array<{
      snapshotTime: Date;
      price: Money;
      greenPoints: Money;
      blackHoleAmount: Money;
      circulationPool: Money;
      effectiveDenominator: Money;
      minuteBurnRate: Money;
    }>,
    periodMinutes: number,
    period: string,
  ): Array<{
    time: string;
    open: string;
    high: string;
    low: string;
    close: string;
    volume: string;
  }> {
    if (snapshots.length === 0) return [];

    const klineMap = new Map<
      number,
      {
        time: Date;
        prices: Decimal[];
      }
    >();

    // 按周期分组
    for (const snapshot of snapshots) {
      const periodStart = this.getPeriodStart(snapshot.snapshotTime, periodMinutes, period);
      const key = periodStart.getTime();

      if (!klineMap.has(key)) {
        klineMap.set(key, {
          time: periodStart,
          prices: [],
        });
      }

      klineMap.get(key)!.prices.push(snapshot.price.value);
    }

    // 转换为K线格式
    const klines: Array<{
      time: string;
      open: string;
      high: string;
      low: string;
      close: string;
      volume: string;
    }> = [];

    const sortedKeys = Array.from(klineMap.keys()).sort((a, b) => a - b);

    for (const key of sortedKeys) {
      const data = klineMap.get(key)!;
      const prices = data.prices;

      if (prices.length === 0) continue;

      const open = prices[0];
      const close = prices[prices.length - 1];
      const high = Decimal.max(...prices);
      const low = Decimal.min(...prices);

      klines.push({
        time: data.time.toISOString(),
        open: open.toFixed(18),
        high: high.toFixed(18),
        low: low.toFixed(18),
        close: close.toFixed(18),
        volume: '0', // 该系统无交易量概念
      });
    }

    return klines;
  }

  /**
   * 获取周期的起始时间
   *
   * 固定周期（1m ~ 1d）：基于 Unix epoch 的模运算，确保相同周期的快照分到同一组。
   * 日历周期（1w / 1M / 1y）：按日历边界对齐，使 K 线与自然周/月/年一致：
   *   - 1w：对齐到本周一 00:00 UTC（ISO 周标准）
   *   - 1M：对齐到本月1日 00:00 UTC
   *   - 1y：对齐到本年1月1日 00:00 UTC
   */
  private getPeriodStart(time: Date, periodMinutes: number, period: string): Date {
    if (period === '1w') {
      // 周K线：对齐到周一 00:00 UTC
      const d = new Date(Date.UTC(time.getUTCFullYear(), time.getUTCMonth(), time.getUTCDate()));
      const day = d.getUTCDay(); // 0=Sun, 1=Mon, ..., 6=Sat
      const diff = day === 0 ? 6 : day - 1; // 周日距周一6天，周一距周一0天
      d.setUTCDate(d.getUTCDate() - diff);
      return d;
    }
    if (period === '1M') {
      // 月K线：对齐到月初1日 00:00 UTC
      return new Date(Date.UTC(time.getUTCFullYear(), time.getUTCMonth(), 1));
    }
    if (period === '1y') {
      // 年K线：对齐到年初1月1日 00:00 UTC
      return new Date(Date.UTC(time.getUTCFullYear(), 0, 1));
    }
    // 固定周期（1m, 5m, 15m, 30m, 1h, 4h, 1d）：epoch 模运算
    const msPerPeriod = periodMinutes * 60 * 1000;
    const periodStart = Math.floor(time.getTime() / msPerPeriod) * msPerPeriod;
    return new Date(periodStart);
  }
}
