// GDSL Margin Service
// Real-time margining across 33 SCBs
// IM = exposure * volatility * SRI_factor
// VM = mark_to_market_new - mark_to_market_old

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 MarginCalculationRequest {
  contractId: string;
  exposure: string;
  volatility: string;
  markToMarket: string;
  previousMarkToMarket?: string;
}

export interface MarginCalculationResult {
  marginId: string;
  initialMargin: string;
  variationMargin: string;
  totalMargin: string;
  sriFactor: string;
}

export class GdslMarginService {
  /**
   * Calculate initial margin (IM)
   * IM = exposure * volatility * SRI_factor
   */
  async calculateInitialMargin(
    contractId: string,
    exposure: Decimal,
    volatility: Decimal,
    party1BankId: string,
    party2BankId: string
  ): Promise<Decimal> {
    // Get SRI factors for both parties
    const sri1 = await this.getSRIFactor(party1BankId);
    const sri2 = await this.getSRIFactor(party2BankId);
    
    // Use the higher risk (higher SRI factor) for margin calculation
    const sriFactor = sri1.greaterThan(sri2) ? sri1 : sri2;
    
    // IM = exposure * volatility * SRI_factor
    const initialMargin = exposure.mul(volatility).mul(sriFactor);
    
    return initialMargin;
  }

  /**
   * Calculate variation margin (VM)
   * VM = mark_to_market_new - mark_to_market_old
   */
  async calculateVariationMargin(
    markToMarketNew: Decimal,
    markToMarketOld: Decimal
  ): Promise<Decimal> {
    return markToMarketNew.minus(markToMarketOld);
  }

  /**
   * Get SRI factor from SRI score
   * Converts SRI score (0-100, higher = riskier) to factor (0-1, higher = riskier)
   */
  private async getSRIFactor(sovereignBankId: string): Promise<Decimal> {
    const sri = await prisma.sovereignRiskIndex.findFirst({
      where: {
        sovereignBankId,
        status: 'active',
      },
      orderBy: { calculatedAt: 'desc' },
    });

    if (!sri) {
      // Default to moderate risk if SRI not available
      return new Decimal(0.5);
    }

    // Convert SRI score (0-100) to factor (0-1)
    // Higher SRI score = higher risk = higher factor
    const sriScore = parseFloat(sri.sriScore.toString());
    const sriFactor = new Decimal(sriScore).div(100);
    
    return sriFactor;
  }

  /**
   * Calculate and post margin
   */
  async calculateAndPostMargin(
    request: MarginCalculationRequest
  ): Promise<MarginCalculationResult> {
    const contract = await prisma.derivativeContract.findUnique({
      where: { contractId: request.contractId },
    });

    if (!contract) {
      throw new Error(`Derivative contract not found: ${request.contractId}`);
    }

    const exposure = new Decimal(request.exposure);
    const volatility = new Decimal(request.volatility);
    const markToMarket = new Decimal(request.markToMarket);
    const previousMarkToMarket = request.previousMarkToMarket 
      ? new Decimal(request.previousMarkToMarket) 
      : new Decimal(0);

    // Calculate initial margin
    const initialMargin = await this.calculateInitialMargin(
      request.contractId,
      exposure,
      volatility,
      contract.party1BankId,
      contract.party2BankId
    );

    // Calculate variation margin
    const variationMargin = await this.calculateVariationMargin(
      markToMarket,
      previousMarkToMarket
    );

    // Get SRI factor for logging
    const sri1 = await this.getSRIFactor(contract.party1BankId);
    const sri2 = await this.getSRIFactor(contract.party2BankId);
    const sriFactor = sri1.greaterThan(sri2) ? sri1 : sri2;

    // Create margin record
    const marginId = `MARGIN-${uuidv4()}`;
    const margin = await prisma.derivativeMargin.create({
      data: {
        marginId,
        contractId: request.contractId,
        marginType: 'initial_margin',
        amount: initialMargin,
        exposure: exposure,
        volatility: volatility,
        sriFactor: sriFactor,
        markToMarket: markToMarket,
        previousMarkToMarket: previousMarkToMarket,
        status: 'pending',
      },
    });

    // Create variation margin record if VM is non-zero
    if (!variationMargin.isZero()) {
      await prisma.derivativeMargin.create({
        data: {
          marginId: `MARGIN-${uuidv4()}`,
          contractId: request.contractId,
          marginType: 'variation_margin',
          amount: variationMargin.abs(),
          markToMarket: markToMarket,
          previousMarkToMarket: previousMarkToMarket,
          status: 'pending',
        },
      });
    }

    const totalMargin = initialMargin.plus(variationMargin.abs());

    return {
      marginId: margin.marginId,
      initialMargin: initialMargin.toString(),
      variationMargin: variationMargin.toString(),
      totalMargin: totalMargin.toString(),
      sriFactor: sriFactor.toString(),
    };
  }

  /**
   * Post margin to ledger
   */
  async postMargin(marginId: string): Promise<void> {
    await prisma.derivativeMargin.update({
      where: { marginId },
      data: {
        status: 'posted',
        postedAt: new Date(),
      },
    });
  }

  /**
   * Get margin requirements for a contract
   */
  async getMarginRequirements(contractId: string): Promise<{
    initialMargin: string;
    variationMargin: string;
    totalMargin: string;
  }> {
    const margins = await prisma.derivativeMargin.findMany({
      where: {
        contractId,
        status: { in: ['pending', 'posted'] },
      },
    });

    let initialMargin = new Decimal(0);
    let variationMargin = new Decimal(0);

    for (const margin of margins) {
      if (margin.marginType === 'initial_margin') {
        initialMargin = initialMargin.plus(margin.amount);
      } else if (margin.marginType === 'variation_margin') {
        variationMargin = variationMargin.plus(margin.amount);
      }
    }

    const totalMargin = initialMargin.plus(variationMargin);

    return {
      initialMargin: initialMargin.toString(),
      variationMargin: variationMargin.toString(),
      totalMargin: totalMargin.toString(),
    };
  }
}

export const gdslMarginService = new GdslMarginService();

