// Liquidity Score Service
// Real-time liquidity scoring
// Buffer level monitoring
// Risk-based scoring algorithm
// Alert triggers

import { Decimal } from '@prisma/client/runtime/library';
import { v4 as uuidv4 } from 'uuid';
import prisma from '@/shared/database/prisma';
import { logger } from '@/infrastructure/monitoring/logger';

export interface LiquidityScoreFactors {
  bufferLevel?: Decimal;
  settlementVolume?: Decimal;
  fxVolatility?: Decimal;
  reserveAdequacy?: Decimal;
  creditLineUsage?: Decimal;
}

export class LiquidityScoreService {
  /**
   * Calculate liquidity score for a sovereign bank
   * Score: 0-100 (higher is better)
   * <85% triggers Tier 1 automatic liquidity access
   */
  async calculateLiquidityScore(
    sovereignBankId: string
  ): Promise<string> {
    // Get liquidity factors
    const factors = await this.getLiquidityFactors(sovereignBankId);

    // Calculate buffer level score (0-40 points)
    const bufferScore = this.calculateBufferScore(factors.bufferLevel);

    // Calculate settlement volume score (0-25 points)
    const volumeScore = this.calculateVolumeScore(factors.settlementVolume);

    // Calculate FX volatility score (0-15 points)
    const volatilityScore = this.calculateVolatilityScore(factors.fxVolatility);

    // Calculate reserve adequacy score (0-10 points)
    const reserveScore = this.calculateReserveScore(factors.reserveAdequacy);

    // Calculate credit line usage score (0-10 points)
    const creditScore = this.calculateCreditScore(factors.creditLineUsage);

    // Calculate total score
    const totalScore = bufferScore
      .plus(volumeScore)
      .plus(volatilityScore)
      .plus(reserveScore)
      .plus(creditScore);

    // Ensure score is between 0-100
    const score = Decimal.max(new Decimal(0), Decimal.min(new Decimal(100), totalScore));

    // Get or create score record
    const scoreId = `LS-${sovereignBankId}-${uuidv4()}`;

    await prisma.liquidityScore.create({
      data: {
        scoreId,
        sovereignBankId,
        score,
        bufferLevel: factors.bufferLevel || null,
        riskFactors: factors as any,
        calculatedAt: new Date(),
      },
    });

    // Trigger alerts if score is low
    if (score.lt(85)) {
      await this.triggerLowLiquidityAlert(sovereignBankId, score);
    }

    return score.toString();
  }

  /**
   * Get liquidity factors for a sovereign bank
   */
  private async getLiquidityFactors(
    sovereignBankId: string
  ): Promise<LiquidityScoreFactors> {
    // Get buffer level from liquidity pools
    const liquidityPools = await prisma.liquidityPool.findMany({
      where: {
        sovereignBankId,
      },
    });

    const totalAvailable = liquidityPools.reduce(
      (sum, pool) => sum.plus(pool.availableLiquidity),
      new Decimal(0)
    );

    const totalReserved = liquidityPools.reduce(
      (sum, pool) => sum.plus(pool.reservedLiquidity),
      new Decimal(0)
    );

    const bufferLevel = totalAvailable.div(totalAvailable.plus(totalReserved));

    // Get settlement volume (last 24 hours)
    const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
    const settlements = await prisma.atomicSettlement.findMany({
      where: {
        OR: [
          { sourceBankId: sovereignBankId },
          { destinationBankId: sovereignBankId },
        ],
        createdAt: {
          gte: oneDayAgo,
        },
        status: 'settled',
      },
    });

    const settlementVolume = settlements.reduce(
      (sum, settlement) => sum.plus(settlement.amount),
      new Decimal(0)
    );

    // Get FX volatility (would query FX service in production)
    const fxVolatility = new Decimal(0.05); // Default 5%

    // Get reserve adequacy (would calculate from reserves in production)
    const reserveAdequacy = new Decimal(1.0); // Default 100%

    // Get credit line usage
    const creditLines = await prisma.interbankCreditLine.findMany({
      where: {
        sovereignBankId,
        status: 'active',
      },
    });

    const totalCredit = creditLines.reduce(
      (sum, line) => sum.plus(line.creditLimit),
      new Decimal(0)
    );

    const usedCredit = creditLines.reduce(
      (sum, line) => sum.plus(line.usedAmount),
      new Decimal(0)
    );

    const creditLineUsage = totalCredit.gt(0)
      ? usedCredit.div(totalCredit)
      : new Decimal(0);

    return {
      bufferLevel,
      settlementVolume,
      fxVolatility,
      reserveAdequacy,
      creditLineUsage,
    };
  }

  /**
   * Calculate buffer score (0-40 points)
   */
  private calculateBufferScore(bufferLevel?: Decimal): Decimal {
    if (!bufferLevel) {
      return new Decimal(0);
    }

    // Higher buffer = higher score
    // 100% buffer = 40 points, 50% buffer = 20 points, 0% = 0 points
    return bufferLevel.mul(40);
  }

  /**
   * Calculate volume score (0-25 points)
   */
  private calculateVolumeScore(settlementVolume?: Decimal): Decimal {
    if (!settlementVolume) {
      return new Decimal(25); // Full score if no volume
    }

    // Lower volume relative to capacity = higher score
    // In production, would compare against capacity
    // For now, assume moderate volume gets full score
    return new Decimal(25);
  }

  /**
   * Calculate volatility score (0-15 points)
   */
  private calculateVolatilityScore(fxVolatility?: Decimal): Decimal {
    if (!fxVolatility) {
      return new Decimal(15); // Full score if no volatility
    }

    // Lower volatility = higher score
    // 0% volatility = 15 points, 10% volatility = 0 points
    const score = new Decimal(15).minus(fxVolatility.mul(150));
    return Decimal.max(new Decimal(0), score);
  }

  /**
   * Calculate reserve score (0-10 points)
   */
  private calculateReserveScore(reserveAdequacy?: Decimal): Decimal {
    if (!reserveAdequacy) {
      return new Decimal(0);
    }

    // Higher adequacy = higher score
    return reserveAdequacy.mul(10);
  }

  /**
   * Calculate credit score (0-10 points)
   */
  private calculateCreditScore(creditLineUsage?: Decimal): Decimal {
    if (!creditLineUsage) {
      return new Decimal(10); // Full score if no credit usage
    }

    // Lower usage = higher score
    // 0% usage = 10 points, 100% usage = 0 points
    return new Decimal(10).minus(creditLineUsage.mul(10));
  }

  /**
   * Get current liquidity score
   */
  async getLiquidityScore(sovereignBankId: string) {
    return await prisma.liquidityScore.findFirst({
      where: {
        sovereignBankId,
      },
      orderBy: { calculatedAt: 'desc' },
    });
  }

  /**
   * Monitor buffer levels
   */
  async monitorBufferLevels(sovereignBankId: string): Promise<void> {
    const liquidityPools = await prisma.liquidityPool.findMany({
      where: {
        sovereignBankId,
      },
    });

    for (const pool of liquidityPools) {
      const bufferRatio = pool.availableLiquidity.div(
        pool.totalLiquidity.gt(0) ? pool.totalLiquidity : new Decimal(1)
      );

      // Alert if buffer is low (<20%)
      if (bufferRatio.lt(0.2)) {
        await this.triggerBufferAlert(sovereignBankId, pool.currencyCode, bufferRatio);
      }
    }
  }

  /**
   * Trigger low liquidity alert
   */
  private async triggerLowLiquidityAlert(
    sovereignBankId: string,
    score: Decimal
  ): Promise<void> {
    // In production, this would send alerts
    // For now, just log
    logger.warn('Low liquidity alert', {
      sovereignBankId,
      score: score.toString(),
    });
  }

  /**
   * Trigger buffer alert
   */
  private async triggerBufferAlert(
    sovereignBankId: string,
    currencyCode: string,
    bufferRatio: Decimal
  ): Promise<void> {
    // In production, this would send alerts
    // For now, just log
    logger.warn('Low buffer alert', {
      sovereignBankId,
      currencyCode,
      bufferRatio: bufferRatio.toString(),
    });
  }

  /**
   * Get liquidity scores for all banks
   */
  async getAllLiquidityScores(limit: number = 100) {
    // Get latest score for each bank
    const scores = await prisma.liquidityScore.findMany({
      orderBy: { calculatedAt: 'desc' },
      take: limit * 10, // Get more to filter unique
    });

    // Group by bank and get latest
    const latestScores = new Map<string, any>();

    for (const score of scores) {
      if (!latestScores.has(score.sovereignBankId)) {
        latestScores.set(score.sovereignBankId, score);
      }
    }

    return Array.from(latestScores.values())
      .sort((a, b) => b.score.toNumber() - a.score.toNumber())
      .slice(0, limit);
  }
}

export const liquidityScoreService = new LiquidityScoreService();

