// GRU SDR Service
// Volume II: GRU-Backed SDR (Special Drawing Right) Alternative
// SDR_GRU = 0.40 GRU + 0.30 XAU + 0.30 Basket_FX

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 { gruIndexService } from './gru-index.service';
import { gruValuationService } from './gru-valuation.service';


export interface SdrComposition {
  gruWeight: Decimal; // 0.40 (40%)
  xauWeight: Decimal; // 0.30 (30%)
  fxBasketWeight: Decimal; // 0.30 (30%)
  fxBasket: Record<string, Decimal>; // USD, EUR, CNY, etc. with weights
}

export interface SdrValuationResult {
  sdrId: string;
  totalValue: Decimal;
  gruValue: Decimal;
  xauValue: Decimal;
  fxBasketValue: Decimal;
  valuationDate: Date;
}

export interface ConvertSdrRequest {
  sdrId: string;
  amount: string;
  sourceCurrency: string;
  targetCurrency: string;
}

export class GruSdrService {
  private readonly DEFAULT_GRU_WEIGHT = new Decimal(0.40);
  private readonly DEFAULT_XAU_WEIGHT = new Decimal(0.30);
  private readonly DEFAULT_FX_BASKET_WEIGHT = new Decimal(0.30);

  /**
   * Initialize or get default SDR_GRU instrument
   */
  async initializeSdr(
    fxBasket?: Record<string, number>
  ): Promise<string> {
    // Check if SDR already exists
    const existing = await prisma.gruSdrInstrument.findFirst({
      where: { sdrName: 'SDR_GRU' },
    });

    if (existing) {
      logger.info('GRU SDR: SDR_GRU already exists', { sdrId: existing.sdrId });
      return existing.sdrId;
    }

    // Default FX basket weights (USD, EUR, CNY, GBP, JPY)
    const defaultFxBasket = fxBasket || {
      USD: 0.35,
      EUR: 0.30,
      CNY: 0.20,
      GBP: 0.10,
      JPY: 0.05,
    };

    const sdrId = `SDR-GRU-${uuidv4()}`;

    const sdr = await prisma.gruSdrInstrument.create({
      data: {
        sdrId,
        sdrName: 'SDR_GRU',
        gruWeight: this.DEFAULT_GRU_WEIGHT,
        xauWeight: this.DEFAULT_XAU_WEIGHT,
        fxBasketWeight: this.DEFAULT_FX_BASKET_WEIGHT,
        composition: {
          gru: { weight: 0.40, description: 'GRU component' },
          xau: { weight: 0.30, description: 'Gold/XAU component' },
          fxBasket: { weight: 0.30, currencies: defaultFxBasket },
        },
        currentValue: new Decimal(0), // Will be calculated
        valuationDate: new Date(),
        fxBasket: defaultFxBasket,
        status: 'active',
      },
    });

    // Calculate initial valuation
    await this.calculateValuation(sdrId);

    logger.info('GRU SDR: SDR_GRU initialized', { sdrId });

    return sdrId;
  }

  /**
   * Calculate SDR_GRU valuation
   * SDR_GRU = 0.40 GRU + 0.30 XAU + 0.30 Basket_FX
   */
  async calculateValuation(sdrId: string): Promise<SdrValuationResult> {
    logger.info('GRU SDR: Calculating valuation', { sdrId });

    const sdr = await prisma.gruSdrInstrument.findUnique({
      where: { sdrId },
    });

    if (!sdr) {
      throw new Error(`SDR instrument not found: ${sdrId}`);
    }

    // Get GRU value (using LiXAU index as base)
    const gruIndex = await gruIndexService.getIndex('LiXAU');
    if (!gruIndex) {
      throw new Error('LiXAU index not found');
    }

    // GRU value: 1 M00 GRU = 1 oz XAU (via LiXAU)
    const gruValue = gruIndex.currentValue.times(sdr.gruWeight);

    // XAU value: Direct gold price
    const xauValue = gruIndex.currentValue.times(sdr.xauWeight);

    // FX Basket value: Weighted average of basket currencies
    const fxBasket = sdr.fxBasket as Record<string, number>;
    let fxBasketValue = new Decimal(0);

    // In production, would fetch real-time FX rates
    // For now, use placeholder calculation
    for (const [currency, weight] of Object.entries(fxBasket)) {
      // Placeholder: assume 1.0 base rate (would be real FX rate in production)
      const fxRate = new Decimal(1.0);
      fxBasketValue = fxBasketValue.plus(fxRate.times(weight));
    }

    fxBasketValue = fxBasketValue.times(sdr.fxBasketWeight);

    // Total SDR value
    const totalValue = gruValue.plus(xauValue).plus(fxBasketValue);

    // Save valuation
    const valuationId = `SDR-VAL-${uuidv4()}`;

    await prisma.gruSdrValuation.create({
      data: {
        valuationId,
        sdrId,
        gruValue,
        xauValue,
        fxBasketValue,
        totalValue,
        valuationDate: new Date(),
        metadata: {
          gruIndexValue: gruIndex.currentValue.toString(),
          calculationMethod: 'SDR_GRU = 0.40 GRU + 0.30 XAU + 0.30 Basket_FX',
        },
      },
    });

    // Update SDR current value
    await prisma.gruSdrInstrument.update({
      where: { sdrId },
      data: {
        currentValue: totalValue,
        valuationDate: new Date(),
      },
    });

    logger.info('GRU SDR: Valuation calculated', {
      sdrId,
      totalValue: totalValue.toString(),
    });

    return {
      sdrId,
      totalValue,
      gruValue,
      xauValue,
      fxBasketValue,
      valuationDate: new Date(),
    };
  }

  /**
   * Get current SDR value
   */
  async getCurrentValue(sdrId: string): Promise<Decimal> {
    const sdr = await prisma.gruSdrInstrument.findUnique({
      where: { sdrId },
    });

    if (!sdr) {
      throw new Error(`SDR instrument not found: ${sdrId}`);
    }

    // Recalculate if valuation is stale (older than 1 hour)
    const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000);
    if (sdr.valuationDate < oneHourAgo) {
      await this.calculateValuation(sdrId);
      const updated = await prisma.gruSdrInstrument.findUnique({
        where: { sdrId },
      });
      return updated!.currentValue;
    }

    return sdr.currentValue;
  }

  /**
   * Convert currency to SDR_GRU
   */
  async convertToSdr(
    request: ConvertSdrRequest
  ): Promise<{ sdrAmount: Decimal; conversionRate: Decimal }> {
    logger.info('GRU SDR: Converting to SDR', { request });

    const sdr = await prisma.gruSdrInstrument.findUnique({
      where: { sdrId: request.sdrId },
    });

    if (!sdr) {
      throw new Error(`SDR instrument not found: ${request.sdrId}`);
    }

    // Get current SDR value
    const sdrValue = await this.getCurrentValue(request.sdrId);

    // In production, would fetch real FX rate for sourceCurrency
    // For now, assume 1:1 for simplicity
    const sourceFxRate = new Decimal(1.0);

    const sourceAmount = new Decimal(request.amount);
    const sourceValueInBase = sourceAmount.times(sourceFxRate);

    // Convert to SDR
    const sdrAmount = sourceValueInBase.dividedBy(sdrValue);
    const conversionRate = sdrValue.dividedBy(sourceFxRate);

    // Record transaction
    const transactionId = `SDR-TXN-${uuidv4()}`;

    await prisma.gruSdrTransaction.create({
      data: {
        transactionId,
        sdrId: request.sdrId,
        transactionType: 'conversion',
        amount: sdrAmount,
        sourceCurrency: request.sourceCurrency,
        targetCurrency: 'SDR_GRU',
        conversionRate,
        status: 'completed',
        completedAt: new Date(),
      },
    });

    logger.info('GRU SDR: Conversion completed', {
      transactionId,
      sdrAmount: sdrAmount.toString(),
      conversionRate: conversionRate.toString(),
    });

    return { sdrAmount, conversionRate };
  }

  /**
   * Convert SDR_GRU to currency
   */
  async convertFromSdr(
    sdrId: string,
    sdrAmount: string,
    targetCurrency: string
  ): Promise<{ targetAmount: Decimal; conversionRate: Decimal }> {
    logger.info('GRU SDR: Converting from SDR', {
      sdrId,
      sdrAmount,
      targetCurrency,
    });

    const sdr = await prisma.gruSdrInstrument.findUnique({
      where: { sdrId },
    });

    if (!sdr) {
      throw new Error(`SDR instrument not found: ${sdrId}`);
    }

    // Get current SDR value
    const sdrValue = await this.getCurrentValue(sdrId);

    // In production, would fetch real FX rate for targetCurrency
    const targetFxRate = new Decimal(1.0);

    const sdrAmountDecimal = new Decimal(sdrAmount);
    const targetValueInBase = sdrAmountDecimal.times(sdrValue);

    // Convert to target currency
    const targetAmount = targetValueInBase.dividedBy(targetFxRate);
    const conversionRate = sdrValue.dividedBy(targetFxRate);

    // Record transaction
    const transactionId = `SDR-TXN-${uuidv4()}`;

    await prisma.gruSdrTransaction.create({
      data: {
        transactionId,
        sdrId,
        transactionType: 'conversion',
        amount: sdrAmountDecimal,
        sourceCurrency: 'SDR_GRU',
        targetCurrency,
        conversionRate,
        status: 'completed',
        completedAt: new Date(),
      },
    });

    logger.info('GRU SDR: Conversion completed', {
      transactionId,
      targetAmount: targetAmount.toString(),
      conversionRate: conversionRate.toString(),
    });

    return { targetAmount, conversionRate };
  }

  /**
   * Get SDR instrument
   */
  async getSdr(sdrId: string) {
    return await prisma.gruSdrInstrument.findUnique({
      where: { sdrId },
      include: {
        valuations: {
          orderBy: { valuationDate: 'desc' },
          take: 10,
        },
        transactions: {
          orderBy: { createdAt: 'desc' },
          take: 10,
        },
      },
    });
  }

  /**
   * Get SDR valuation history
   */
  async getValuationHistory(sdrId: string, limit: number = 100) {
    return await prisma.gruSdrValuation.findMany({
      where: { sdrId },
      orderBy: { valuationDate: 'desc' },
      take: limit,
    });
  }

  /**
   * Update FX basket weights
   */
  async updateFxBasket(
    sdrId: string,
    fxBasket: Record<string, number>
  ): Promise<void> {
    // Validate weights sum to 1.0
    const totalWeight = Object.values(fxBasket).reduce(
      (sum, weight) => sum + weight,
      0
    );

    if (Math.abs(totalWeight - 1.0) > 0.01) {
      throw new Error(
        `FX basket weights must sum to 1.0, got ${totalWeight}`
      );
    }

    await prisma.gruSdrInstrument.update({
      where: { sdrId },
      data: {
        fxBasket,
        composition: {
          gru: { weight: 0.40 },
          xau: { weight: 0.30 },
          fxBasket: { weight: 0.30, currencies: fxBasket },
        },
      },
    });

    // Recalculate valuation
    await this.calculateValuation(sdrId);

    logger.info('GRU SDR: FX basket updated', { sdrId, fxBasket });
  }
}

export const gruSdrService = new GruSdrService();

