// MACE Optimization Service
// Optimal collateral allocation: argmin(haircuts + fx_cost + liquidity_weight + risk_penalty(SRI))

import prisma from '@/shared/database/prisma';
import { Decimal } from '@prisma/client/runtime/library';
import { v4 as uuidv4 } from 'uuid';
import { sriCalculatorService } from '@/core/risk/sri/sri-calculator.service';


export interface OptimizationRequest {
  requiredAmount: string;
  currencyCode: string;
  availableAssets: Array<{
    assetType: string;
    assetId?: string;
    amount: string;
  }>;
  targetBankId?: string;
}

export interface OptimizationResult {
  optimizationId: string;
  optimalAllocation: Array<{
    assetType: string;
    assetId?: string;
    amount: string;
    cost: string;
  }>;
  totalCost: string;
  status: string;
}

export class MaceOptimizationService {
  /**
   * Calculate optimal collateral allocation
   * argmin(haircuts + fx_cost + liquidity_weight + risk_penalty(SRI))
   */
  async optimizeAllocation(
    request: OptimizationRequest
  ): Promise<OptimizationResult> {
    const requiredAmount = new Decimal(request.requiredAmount);
    const optimizationId = `OPT-${uuidv4()}`;

    // Get haircuts and liquidity weights for each asset type
    const assetCosts = await Promise.all(
      request.availableAssets.map(async (asset) => {
        const cost = await this.calculateAssetCost(
          asset.assetType,
          asset.amount,
          request.currencyCode,
          request.targetBankId
        );
        return {
          ...asset,
          cost: cost.totalCost,
          haircut: cost.haircut,
          fxCost: cost.fxCost,
          liquidityWeight: cost.liquidityWeight,
          riskPenalty: cost.riskPenalty,
        };
      })
    );

    // Sort by total cost (ascending)
    assetCosts.sort((a, b) => a.cost.comparedTo(b.cost));

    // Greedy allocation: use cheapest assets first
    const optimalAllocation: Array<{
      assetType: string;
      assetId?: string;
      amount: string;
      cost: string;
    }> = [];

    let remainingAmount = requiredAmount;
    let totalCost = new Decimal(0);

    for (const asset of assetCosts) {
      if (remainingAmount.isZero()) {
        break;
      }

      const availableAmount = new Decimal(asset.amount);
      const allocationAmount = remainingAmount.lessThan(availableAmount)
        ? remainingAmount
        : availableAmount;

      const allocationCost = asset.cost.mul(allocationAmount).div(requiredAmount);

      optimalAllocation.push({
        assetType: asset.assetType,
        assetId: asset.assetId,
        amount: allocationAmount.toString(),
        cost: allocationCost.toString(),
      });

      totalCost = totalCost.plus(allocationCost);
      remainingAmount = remainingAmount.minus(allocationAmount);
    }

    if (!remainingAmount.isZero()) {
      throw new Error('Insufficient collateral available');
    }

    // Create optimization record
    const optimization = await prisma.collateral_optimizations.create({
      data: {
        id: uuidv4(),
        optimizationId,
        collateralId: uuidv4(), // Would link to actual collateral
        optimizationType: 'allocation',
        optimalAllocation: optimalAllocation,
        totalCost,
        calculationMethod: 'argmin',
        status: 'pending',
        createdAt: new Date(),
        updatedAt: new Date(),
      },
    });

    return {
      optimizationId: optimization.optimizationId,
      optimalAllocation,
      totalCost: totalCost.toString(),
      status: optimization.status,
    };
  }

  /**
   * Calculate asset cost
   */
  private async calculateAssetCost(
    assetType: string,
    amount: string,
    targetCurrency: string,
    targetBankId?: string
  ): Promise<{
    totalCost: Decimal;
    haircut: Decimal;
    fxCost: Decimal;
    liquidityWeight: Decimal;
    riskPenalty: Decimal;
  }> {
    // Get haircut
    const haircut = await this.getHaircut(assetType);
    const haircutCost = new Decimal(amount).mul(haircut).div(100);

    // Get FX cost (if asset currency differs from target)
    const fxCost = await this.getFxCost(assetType, targetCurrency);
    const fxCostAmount = new Decimal(amount).mul(fxCost).div(100);

    // Get liquidity weight
    const liquidityWeight = await this.getLiquidityWeight(assetType);
    const liquidityCost = new Decimal(amount).mul(liquidityWeight).div(100);

    // Get risk penalty (SRI-based)
    const riskPenalty = targetBankId
      ? await this.getRiskPenalty(targetBankId)
      : new Decimal(0);
    const riskCost = new Decimal(amount).mul(riskPenalty).div(100);

    // Total cost = haircuts + fx_cost + liquidity_weight + risk_penalty
    const totalCost = haircutCost.plus(fxCostAmount).plus(liquidityCost).plus(riskCost);

    return {
      totalCost,
      haircut,
      fxCost,
      liquidityWeight,
      riskPenalty,
    };
  }

  /**
   * Get haircut for asset type
   */
  private async getHaircut(assetType: string): Promise<Decimal> {
    const haircut = await prisma.collateral_haircuts.findFirst({
      where: {
        assetType,
        status: 'active',
      },
    });

    return haircut ? haircut.haircutRate : new Decimal(5); // Default 5%
  }

  /**
   * Get FX cost
   */
  private async getFxCost(assetType: string, targetCurrency: string): Promise<Decimal> {
    // In production, would calculate actual FX conversion cost
    // For now, return minimal cost
    return new Decimal(0.1); // 0.1%
  }

  /**
   * Get liquidity weight
   */
  private async getLiquidityWeight(assetType: string): Promise<Decimal> {
    const liquidity = await prisma.collateral_liquidities.findFirst({
      where: {
        assetType,
        status: 'active',
      },
    });

    return liquidity ? liquidity.liquidityWeight : new Decimal(1); // Default 1%
  }

  /**
   * Get risk penalty (SRI-based)
   */
  private async getRiskPenalty(sovereignBankId: string): Promise<Decimal> {
    const sri = await prisma.sovereign_risk_indices.findFirst({
      where: {
        sovereignBankId,
        status: 'active',
      },
      orderBy: { calculatedAt: 'desc' },
    });

    if (!sri) {
      return new Decimal(0);
    }

    // Convert SRI score (0-100) to penalty (0-5%)
    const sriScore = parseFloat(sri.sriScore.toString());
    const penalty = new Decimal(sriScore).div(100).mul(5); // Max 5% penalty

    return penalty;
  }

  /**
   * Apply optimization
   */
  async applyOptimization(optimizationId: string): Promise<void> {
    await prisma.collateral_optimizations.update({
      where: { optimizationId },
      data: {
        status: 'applied',
        appliedAt: new Date(),
      },
    });
  }
}

export const maceOptimizationService = new MaceOptimizationService();

