// GRU Derivatives Service
// Derivative lifecycle management (futures, swaps, options)

import prisma from '@/shared/database/prisma';
import { Decimal } from '@prisma/client/runtime/library';
import { v4 as uuidv4 } from 'uuid';
import { logger } from '@/infrastructure/monitoring/logger';
import { gruDerivativesPricingService } from './gru-derivatives-pricing.service';
import { gruIndexService, GruIndexCode } from './gru-index.service';


export interface GruFuturesRequest {
  sovereignBankId: string;
  counterpartyBankId?: string;
  contractType: 'front_month' | 'quarterly' | 'annual';
  marginClass: 'GRF-A' | 'GRF-B' | 'GRF-C';
  contractSize: string;
  deliveryDate: Date;
  underlyingIndex: GruIndexCode;
}

export interface GruSwapRequest {
  sovereignBankId: string;
  counterpartyBankId?: string;
  swapType: 'GRU_USD' | 'GRU_XAU' | 'GRU_SSU';
  fixedRate: string;
  floatingRateIndex: string;
  paymentFrequency: 'daily' | 'weekly' | 'monthly' | 'quarterly' | 'annually';
  notionalAmount: string;
  maturityDate: Date;
  underlyingIndex?: GruIndexCode;
}

export interface GruOptionRequest {
  sovereignBankId: string;
  counterpartyBankId?: string;
  optionType: 'call' | 'put';
  underlyingIndex: GruIndexCode;
  strikePrice: string;
  expirationDate: Date;
  exerciseType: 'american' | 'european';
  settlementType: 'physical' | 'cash';
  quantity: string;
}

export class GruDerivativesService {
  /**
   * Create GRU futures contract
   */
  async createFuturesContract(
    request: GruFuturesRequest
  ): Promise<string> {
    logger.info('GRU Derivatives: Creating futures contract', { request });

    // Get underlying index
    const index = await gruIndexService.getIndex(request.underlyingIndex);
    if (!index) {
      throw new Error(`Index not found: ${request.underlyingIndex}`);
    }

    // Calculate margin requirements based on margin class
    const marginRequirements = this.calculateMarginRequirements(
      request.marginClass,
      new Decimal(request.contractSize),
      index.currentValue
    );

    // Get pricing
    const volatility = await gruDerivativesPricingService.getDefaultVolatility(
      request.underlyingIndex
    );
    const liquidityPremium =
      await gruDerivativesPricingService.calculateLiquidityPremium(
        request.underlyingIndex,
        new Decimal(request.contractSize)
      );
    const arbitrageSpread =
      await gruDerivativesPricingService.calculateMetalArbitrageSpread(
        request.underlyingIndex
      );

    const pricing = await gruDerivativesPricingService.calculateDerivativePrice(
      {
        indexCode: request.underlyingIndex,
        indexBaseValue: index.currentValue,
        volatility,
        liquidityPremium,
        metalArbitrageSpread: arbitrageSpread,
      }
    );

    // Create derivative
    const derivativeId = `GRU-DERIV-${uuidv4()}`;
    const futuresId = `GRU-FUT-${uuidv4()}`;

    await prisma.gruDerivative.create({
      data: {
        derivativeId,
        derivativeType: 'futures',
        instrumentType: 'GRU_FUTURES',
        sovereignBankId: request.sovereignBankId,
        counterpartyBankId: request.counterpartyBankId || null,
        notionalAmount: new Decimal(request.contractSize),
        contractPrice: pricing.finalPrice,
        markToMarket: pricing.finalPrice,
        settlementCurrency: 'GRU',
        status: 'active',
        expirationDate: request.deliveryDate,
        futuresContract: {
          create: {
            futuresId,
            contractType: request.contractType,
            marginClass: request.marginClass,
            marginRequirement: marginRequirements.total,
            maintenanceMargin: marginRequirements.maintenance,
            initialMargin: marginRequirements.initial,
            contractSize: new Decimal(request.contractSize),
            tickSize: new Decimal(0.01),
            settlementPrice: pricing.finalPrice,
            lastPrice: pricing.finalPrice,
            openInterest: new Decimal(request.contractSize),
            volume: new Decimal(0),
            deliveryDate: request.deliveryDate,
          },
        },
      },
    });

    logger.info('GRU Derivatives: Futures contract created', {
      derivativeId,
      futuresId,
    });

    return derivativeId;
  }

  /**
   * Create GRU swap
   */
  async createSwap(request: GruSwapRequest): Promise<string> {
    logger.info('GRU Derivatives: Creating swap', { request });

    const derivativeId = `GRU-DERIV-${uuidv4()}`;
    const swapId = `GRU-SWAP-${uuidv4()}`;

    // Calculate next payment date based on frequency
    const nextPaymentDate = this.calculateNextPaymentDate(
      request.paymentFrequency
    );

    await prisma.gruDerivative.create({
      data: {
        derivativeId,
        derivativeType: 'swap',
        instrumentType: 'GRU_SWAP',
        sovereignBankId: request.sovereignBankId,
        counterpartyBankId: request.counterpartyBankId || null,
        notionalAmount: new Decimal(request.notionalAmount),
        contractPrice: new Decimal(request.fixedRate),
        markToMarket: new Decimal(request.fixedRate),
        settlementCurrency: 'GRU',
        status: 'active',
        expirationDate: request.maturityDate,
        swap: {
          create: {
            swapId,
            swapType: request.swapType,
            fixedRate: new Decimal(request.fixedRate),
            floatingRateIndex: request.floatingRateIndex,
            paymentFrequency: request.paymentFrequency,
            notionalAmount: new Decimal(request.notionalAmount),
            nextPaymentDate,
            maturityDate: request.maturityDate,
            accruedInterest: new Decimal(0),
          },
        },
      },
    });

    logger.info('GRU Derivatives: Swap created', { derivativeId, swapId });

    return derivativeId;
  }

  /**
   * Create GRU option
   */
  async createOption(request: GruOptionRequest): Promise<string> {
    logger.info('GRU Derivatives: Creating option', { request });

    // Get underlying index
    const index = await gruIndexService.getIndex(request.underlyingIndex);
    if (!index) {
      throw new Error(`Index not found: ${request.underlyingIndex}`);
    }

    // Calculate option premium using Black-Scholes
    const volatility =
      await gruDerivativesPricingService.getDefaultVolatility(
        request.underlyingIndex
      );
    const riskFreeRate = new Decimal(0.05); // 5% risk-free rate

    const timeToExpiration =
      (new Date(request.expirationDate).getTime() - Date.now()) /
      (1000 * 60 * 60 * 24 * 365);

    const spotPrice = index.currentValue;
    const strikePrice = new Decimal(request.strikePrice);

    // Calculate Greeks
    const greeks =
      gruDerivativesPricingService.calculateOptionGreeks(
        spotPrice,
        strikePrice,
        timeToExpiration,
        volatility,
        riskFreeRate,
        request.optionType
      );

    // Calculate premium (simplified Black-Scholes)
    const premium = this.calculateOptionPremium(
      spotPrice,
      strikePrice,
      timeToExpiration,
      volatility,
      riskFreeRate,
      request.optionType
    );

    const derivativeId = `GRU-DERIV-${uuidv4()}`;
    const optionId = `GRU-OPT-${uuidv4()}`;

    await prisma.gruDerivative.create({
      data: {
        derivativeId,
        derivativeType: 'option',
        instrumentType: 'GRU_OPTION',
        sovereignBankId: request.sovereignBankId,
        counterpartyBankId: request.counterpartyBankId || null,
        notionalAmount: new Decimal(request.quantity),
        contractPrice: premium,
        markToMarket: premium,
        settlementCurrency: request.settlementType === 'cash' ? 'SSU' : 'GRU',
        status: 'active',
        expirationDate: request.expirationDate,
        option: {
          create: {
            optionId,
            optionType: request.optionType,
            underlyingIndex: request.underlyingIndex,
            strikePrice,
            premium,
            expirationDate: request.expirationDate,
            exerciseType: request.exerciseType,
            settlementType: request.settlementType,
            settlementCurrency: request.settlementType === 'cash' ? 'SSU' : 'GRU',
            quantity: new Decimal(request.quantity),
            intrinsicValue: this.calculateIntrinsicValue(
              spotPrice,
              strikePrice,
              request.optionType
            ),
            timeValue: premium.minus(
              this.calculateIntrinsicValue(spotPrice, strikePrice, request.optionType)
            ),
            delta: greeks.delta,
            gamma: greeks.gamma,
            theta: greeks.theta,
            vega: greeks.vega,
          },
        },
      },
    });

    logger.info('GRU Derivatives: Option created', {
      derivativeId,
      optionId,
      premium: premium.toString(),
    });

    return derivativeId;
  }

  /**
   * Calculate margin requirements based on margin class
   */
  private calculateMarginRequirements(
    marginClass: 'GRF-A' | 'GRF-B' | 'GRF-C',
    contractSize: Decimal,
    indexValue: Decimal
  ): {
    initial: Decimal;
    maintenance: Decimal;
    total: Decimal;
  } {
    const notionalValue = contractSize.times(indexValue);

    const marginRates: Record<string, { initial: Decimal; maintenance: Decimal }> =
      {
        'GRF-A': { initial: new Decimal(0.05), maintenance: new Decimal(0.04) }, // 5% initial, 4% maintenance
        'GRF-B': { initial: new Decimal(0.10), maintenance: new Decimal(0.08) }, // 10% initial, 8% maintenance
        'GRF-C': { initial: new Decimal(0.15), maintenance: new Decimal(0.12) }, // 15% initial, 12% maintenance
      };

    const rates = marginRates[marginClass];

    return {
      initial: notionalValue.times(rates.initial),
      maintenance: notionalValue.times(rates.maintenance),
      total: notionalValue.times(rates.initial),
    };
  }

  /**
   * Calculate next payment date based on frequency
   */
  private calculateNextPaymentDate(
    frequency: 'daily' | 'weekly' | 'monthly' | 'quarterly' | 'annually'
  ): Date {
    const now = new Date();
    const next = new Date(now);

    switch (frequency) {
      case 'daily':
        next.setDate(next.getDate() + 1);
        break;
      case 'weekly':
        next.setDate(next.getDate() + 7);
        break;
      case 'monthly':
        next.setMonth(next.getMonth() + 1);
        break;
      case 'quarterly':
        next.setMonth(next.getMonth() + 3);
        break;
      case 'annually':
        next.setFullYear(next.getFullYear() + 1);
        break;
    }

    return next;
  }

  /**
   * Calculate option premium using Black-Scholes
   */
  private calculateOptionPremium(
    spotPrice: Decimal,
    strikePrice: Decimal,
    timeToExpiration: number,
    volatility: Decimal,
    riskFreeRate: Decimal,
    optionType: 'call' | 'put'
  ): Decimal {
    const S = spotPrice;
    const K = strikePrice;
    const T = Math.max(timeToExpiration, 0.001);
    const sigma = volatility;
    const r = riskFreeRate;

    // d1 and d2
    const d1Numerator = S.dividedBy(K).ln()
      .plus(r.plus(sigma.pow(2).dividedBy(2)).times(T));
    const d1Denominator = sigma.times(Math.sqrt(T));
    const d1 = d1Numerator.dividedBy(d1Denominator);
    const d2 = d1.minus(sigma.times(Math.sqrt(T)));

    // Normal CDF
    const N_d1 = this.normalCDF(d1);
    const N_d2 = this.normalCDF(d2);
    const N_neg_d1 = new Decimal(1).minus(N_d1);
    const N_neg_d2 = new Decimal(1).minus(N_d2);

    // Black-Scholes formula
    if (optionType === 'call') {
      const callPrice = S
        .times(N_d1)
        .minus(K.times(Math.exp(-r.toNumber() * T)).times(N_d2));
      return callPrice;
    } else {
      const putPrice = K
        .times(Math.exp(-r.toNumber() * T))
        .times(N_neg_d2)
        .minus(S.times(N_neg_d1));
      return putPrice;
    }
  }

  /**
   * Calculate intrinsic value
   */
  private calculateIntrinsicValue(
    spotPrice: Decimal,
    strikePrice: Decimal,
    optionType: 'call' | 'put'
  ): Decimal {
    if (optionType === 'call') {
      return Decimal.max(spotPrice.minus(strikePrice), 0);
    } else {
      return Decimal.max(strikePrice.minus(spotPrice), 0);
    }
  }

  /**
   * Normal CDF approximation
   */
  private normalCDF(x: Decimal): Decimal {
    const t = new Decimal(1).dividedBy(new Decimal(1).plus(0.2316419.times(x.abs())));
    const d = new Decimal(0.3989423)
      .times(Math.exp(-x.pow(2).dividedBy(2).toNumber()));
    const p = d
      .times(t)
      .times(
        new Decimal(0.3193815)
          .minus(
            t.times(
              new Decimal(0.3565638)
                .minus(
                  t.times(
                    new Decimal(1.781478)
                      .minus(t.times(new Decimal(1.821256).minus(t.times(1.330274))))
                  )
                )
            )
          )
      );

    return x.isPositive() ? new Decimal(1).minus(p) : p;
  }

  /**
   * Get derivative by ID
   */
  async getDerivative(derivativeId: string) {
    return await prisma.gruDerivative.findUnique({
      where: { derivativeId },
      include: {
        futuresContract: true,
        swap: {
          include: {
            payments: true,
          },
        },
        option: true,
      },
    });
  }

  /**
   * Get derivatives for a bank
   */
  async getDerivativesForBank(sovereignBankId: string, status?: string) {
    const where: any = { sovereignBankId };
    if (status) {
      where.status = status;
    }

    return await prisma.gruDerivative.findMany({
      where,
      include: {
        futuresContract: true,
        swap: true,
        option: true,
      },
      orderBy: { createdAt: 'desc' },
    });
  }

  /**
   * Mark derivative to market
   */
  async markToMarket(derivativeId: string): Promise<Decimal> {
    const derivative = await this.getDerivative(derivativeId);
    if (!derivative) {
      throw new Error(`Derivative not found: ${derivativeId}`);
    }

    // Recalculate mark-to-market price
    let newMarkToMarket: Decimal;

    if (derivative.futuresContract) {
      // For futures, use current index price
      const index = await gruIndexService.getIndex(derivative.futuresContract.contractType as any);
      newMarkToMarket = index?.currentValue || derivative.contractPrice;
    } else if (derivative.option) {
      // For options, recalculate premium
      const index = await gruIndexService.getIndex(derivative.option.underlyingIndex as any);
      if (!index) {
        newMarkToMarket = derivative.contractPrice;
      } else {
        const volatility = await gruDerivativesPricingService.getDefaultVolatility(
          derivative.option.underlyingIndex as any
        );
        const timeToExpiration =
          (new Date(derivative.option.expirationDate).getTime() - Date.now()) /
          (1000 * 60 * 60 * 24 * 365);
        const riskFreeRate = new Decimal(0.05);

        newMarkToMarket = this.calculateOptionPremium(
          index.currentValue,
          derivative.option.strikePrice,
          timeToExpiration,
          volatility,
          riskFreeRate,
          derivative.option.optionType as 'call' | 'put'
        );
      }
    } else {
      // For swaps, use fixed rate (simplified)
      newMarkToMarket = derivative.contractPrice;
    }

    // Update derivative
    await prisma.gruDerivative.update({
      where: { derivativeId },
      data: {
        markToMarket: newMarkToMarket,
      },
    });

    return newMarkToMarket;
  }

  /**
   * Exercise option
   */
  async exerciseOption(optionId: string): Promise<string> {
    const option = await prisma.gruOption.findUnique({
      where: { optionId },
      include: { derivative: true },
    });

    if (!option || !option.derivative) {
      throw new Error(`Option not found: ${optionId}`);
    }

    if (new Date() > option.expirationDate) {
      throw new Error('Option has expired');
    }

    if (option.exercisedAt) {
      throw new Error('Option already exercised');
    }

    // Get current index value
    const index = await gruIndexService.getIndex(option.underlyingIndex as any);
    if (!index) {
      throw new Error(`Index not found: ${option.underlyingIndex}`);
    }

    // Calculate intrinsic value
    const intrinsicValue = this.calculateIntrinsicValue(
      index.currentValue,
      option.strikePrice,
      option.optionType as 'call' | 'put'
    );

    if (intrinsicValue.isZero()) {
      throw new Error('Option is out of the money');
    }

    // Mark option as exercised
    await prisma.gruOption.update({
      where: { optionId },
      data: {
        exercisedAt: new Date(),
        intrinsicValue,
      },
    });

    // Update derivative status if cash-settled
    if (option.settlementType === 'cash') {
      await prisma.gruDerivative.update({
        where: { derivativeId: option.derivative.derivativeId },
        data: {
          status: 'settled',
          settlementDate: new Date(),
        },
      });
    }

    logger.info('GRU Derivatives: Option exercised', {
      optionId,
      intrinsicValue: intrinsicValue.toString(),
    });

    return optionId;
  }

  /**
   * Process swap payment
   */
  async processSwapPayment(swapId: string): Promise<string> {
    const swap = await prisma.gruSwap.findUnique({
      where: { swapId },
      include: { derivative: true },
    });

    if (!swap || !swap.derivative) {
      throw new Error(`Swap not found: ${swapId}`);
    }

    if (new Date() < swap.nextPaymentDate) {
      throw new Error('Payment date not yet reached');
    }

    // Calculate payment amounts
    const notional = swap.notionalAmount;
    const fixedLegAmount = notional.times(swap.fixedRate);

    // Get floating rate (simplified - in production would fetch from market)
    const floatingRate = new Decimal(0.03); // 3% default floating rate
    const floatingLegAmount = notional.times(floatingRate);

    // Net amount (fixed payer perspective)
    const netAmount = fixedLegAmount.minus(floatingLegAmount);

    // Create payment record
    const paymentId = `GRU-PAY-${uuidv4()}`;

    await prisma.gruSwapPayment.create({
      data: {
        paymentId,
        swapId,
        paymentDate: swap.nextPaymentDate,
        paymentAmount: netAmount.abs(),
        fixedLegAmount,
        floatingLegAmount,
        netAmount,
        status: 'pending',
      },
    });

    // Calculate next payment date
    const nextPaymentDate = this.calculateNextPaymentDate(
      swap.paymentFrequency as any
    );

    // Update swap
    await prisma.gruSwap.update({
      where: { swapId },
      data: {
        nextPaymentDate,
        accruedInterest: new Decimal(0),
      },
    });

    logger.info('GRU Derivatives: Swap payment processed', {
      swapId,
      paymentId,
      netAmount: netAmount.toString(),
    });

    return paymentId;
  }
}

export const gruDerivativesService = new GruDerivativesService();

