// GRU Supranational Service
// Volume II: Supranational Reserve Framework

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';


export type ReserveClass = 'SR-1' | 'SR-2' | 'SR-3';

export interface SupranationalReserveRequest {
  reserveClass: ReserveClass;
  reserveName: string;
  jurisdiction?: string; // For SR-2: EU, AU, ASEAN, GCC, etc.
  totalReserves: string;
}

export interface ReserveAllocationRequest {
  reserveId: string;
  sovereignBankId: string;
  allocationQuota: string;
}

export interface SdrComposition {
  currencies: Array<{
    currencyCode: string;
    weight: number;
  }>;
}

export interface SdrAlternativeResult {
  sdrId: string;
  compositionType: string;
  gruWeight: Decimal;
  xauWeight: Decimal;
  basketWeight: Decimal;
  currentValue: Decimal;
  status: string;
}

export class GruSupranationalService {
  /**
   * Create supranational reserve
   * SR-1: Global Reserve GRU (DBIS + multinational coalitions)
   * SR-2: Regional Reserve GRU (EU, AU, ASEAN, GCC)
   * SR-3: Strategic Commodity Reserve GRU
   */
  async createSupranationalReserve(
    request: SupranationalReserveRequest
  ): Promise<string> {
    logger.info('GRU Supranational: Creating supranational reserve', {
      request,
    });

    // Determine reserve type based on class
    let reserveType: string;
    if (request.reserveClass === 'SR-1') {
      reserveType = 'global_reserve';
    } else if (request.reserveClass === 'SR-2') {
      reserveType = 'regional_reserve';
      if (!request.jurisdiction) {
        throw new Error('Jurisdiction required for SR-2 reserves');
      }
    } else {
      reserveType = 'commodity_reserve';
    }

    const reserveId = `GRU-SR-${uuidv4()}`;

    const reserve = await prisma.gru_supranational_reserves.create({
      data: {
        reserveId,
        reserveClass: request.reserveClass,
        reserveName: request.reserveName,
        reserveType,
        jurisdiction: request.jurisdiction || null,
        totalReserves: new Decimal(request.totalReserves),
        allocatedReserves: new Decimal(0),
        availableReserves: new Decimal(request.totalReserves),
        status: 'active',
      },
    });

    logger.info('GRU Supranational: Supranational reserve created', {
      reserveId,
      reserveClass: request.reserveClass,
    });

    return reserveId;
  }

  /**
   * Allocate reserve quota to sovereign bank
   */
  async allocateReserveQuota(
    request: ReserveAllocationRequest
  ): Promise<string> {
    logger.info('GRU Supranational: Allocating reserve quota', { request });

    // Get reserve
    const reserve = await prisma.gru_supranational_reserves.findUnique({
      where: { reserveId: request.reserveId },
    });

    if (!reserve) {
      throw new Error(`Reserve not found: ${request.reserveId}`);
    }

    // Check available reserves
    const allocationQuota = new Decimal(request.allocationQuota);
    if (allocationQuota.greaterThan(reserve.availableReserves)) {
      throw new Error('Insufficient available reserves');
    }

    // Create allocation
    const allocationId = `GRU-ALLOC-${uuidv4()}`;

    await prisma.gru_reserve_allocations.create({
      data: {
        id: uuidv4(),
        allocationId,
        reserveId: request.reserveId,
        sovereignBankId: request.sovereignBankId,
        allocationQuota,
        allocatedAmount: allocationQuota,
        utilizationRate: new Decimal(0),
        status: 'active',
        createdAt: new Date(),
        updatedAt: new Date(),
      },
    });

    // Update reserve
    const newAllocatedReserves = reserve.allocatedReserves.plus(allocationQuota);
    const newAvailableReserves = reserve.availableReserves.minus(allocationQuota);

    await prisma.gru_supranational_reserves.update({
      where: { reserveId: request.reserveId },
      data: {
        allocatedReserves: newAllocatedReserves,
        availableReserves: newAvailableReserves,
      },
    });

    logger.info('GRU Supranational: Reserve quota allocated', {
      allocationId,
      reserveId: request.reserveId,
      allocationQuota: allocationQuota.toString(),
    });

    return allocationId;
  }

  /**
   * Create regional stabilization fund
   */
  async createRegionalStabilizationFund(
    reserveId: string,
    fundName: string,
    region: string,
    fundSize: string
  ): Promise<string> {
    logger.info('GRU Supranational: Creating regional stabilization fund', {
      reserveId,
      fundName,
      region,
      fundSize,
    });

    // Verify reserve is SR-2 (regional)
    const reserve = await prisma.gru_supranational_reserves.findUnique({
      where: { reserveId },
    });

    if (!reserve) {
      throw new Error(`Reserve not found: ${reserveId}`);
    }

    if (reserve.reserveClass !== 'SR-2') {
      throw new Error('Regional stabilization funds only available for SR-2 reserves');
    }

    const fundId = `GRU-FUND-${uuidv4()}`;

    const fund = await prisma.gruRegionalStabilizationFund.create({
      data: {
        fundId,
        reserveId,
        fundName,
        region,
        fundSize: new Decimal(fundSize),
        availableFunds: new Decimal(fundSize),
        utilizationRate: new Decimal(0),
        status: 'active',
      },
    });

    logger.info('GRU Supranational: Regional stabilization fund created', {
      fundId,
      region,
    });

    return fundId;
  }

  /**
   * Create GRU-Backed SDR Alternative
   * Formula: SDR_GRU = 0.4 GRU + 0.3 XAU + 0.3 Basket_Currencies
   */
  async createGruBackedSdr(
    basketComposition: SdrComposition
  ): Promise<SdrAlternativeResult> {
    logger.info('GRU Supranational: Creating GRU-Backed SDR Alternative', {
      basketComposition,
    });

    // Validate basket weights sum to 30%
    const totalBasketWeight = basketComposition.currencies.reduce(
      (sum, c) => sum + c.weight,
      0
    );

    if (Math.abs(totalBasketWeight - 0.3) > 0.01) {
      throw new Error('Basket currencies must sum to 30% (0.3)');
    }

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

    const gruBaseValue = gruIndex.currentValue;

    // Calculate base SDR value
    // SDR_GRU = 0.4 GRU + 0.3 XAU + 0.3 Basket
    // For simplicity, assume XAU = GRU (since GRU is XAU-anchored)
    // And basket currencies are normalized
    const gruWeight = new Decimal(0.4);
    const xauWeight = new Decimal(0.3);
    const basketWeight = new Decimal(0.3);

    const baseSdrValue = gruBaseValue
      .times(gruWeight)
      .plus(gruBaseValue.times(xauWeight))
      .plus(gruBaseValue.times(basketWeight)); // Simplified basket value

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

    const sdr = await prisma.gru_sdr_alternatives.create({
      data: {
        sdrId,
        compositionType: 'GRU_BACKED_SDR',
        gruWeight,
        xauWeight,
        basketWeight,
        basketCurrencies: basketComposition.currencies,
        baseValue: baseSdrValue,
        currentValue: baseSdrValue,
        status: 'active',
      },
    });

    logger.info('GRU Supranational: GRU-Backed SDR created', {
      sdrId,
      baseValue: baseSdrValue.toString(),
    });

    return {
      sdrId,
      compositionType: 'GRU_BACKED_SDR',
      gruWeight,
      xauWeight,
      basketWeight,
      currentValue: baseSdrValue,
      status: 'active',
    };
  }

  /**
   * Update SDR value
   * Recalculates based on current GRU, XAU, and basket currency values
   */
  async updateSdrValue(sdrId: string): Promise<Decimal> {
    const sdr = await prisma.gru_sdr_alternatives.findUnique({
      where: { sdrId },
    });

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

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

    const currentGruValue = gruIndex.currentValue;

    // Calculate current SDR value
    // SDR_GRU = 0.4 GRU + 0.3 XAU + 0.3 Basket
    const currentSdrValue = currentGruValue
      .times(sdr.gruWeight)
      .plus(currentGruValue.times(sdr.xauWeight)) // XAU ≈ GRU
      .plus(currentGruValue.times(sdr.basketWeight)); // Simplified basket

    // Update SDR
    await prisma.gru_sdr_alternatives.update({
      where: { sdrId },
      data: {
        currentValue: currentSdrValue,
        updatedAt: new Date(),
      },
    });

    logger.info('GRU Supranational: SDR value updated', {
      sdrId,
      currentValue: currentSdrValue.toString(),
    });

    return currentSdrValue;
  }

  /**
   * Get supranational reserve by ID
   */
  async getSupranationalReserve(reserveId: string) {
    return await prisma.gru_supranational_reserves.findUnique({
      where: { reserveId },
      include: {
        allocations: true,
        stabilizationFunds: true,
      },
    });
  }

  /**
   * Get reserves by class
   */
  async getReservesByClass(reserveClass: ReserveClass) {
    return await prisma.gru_supranational_reserves.findMany({
      where: {
        reserveClass,
        status: 'active',
      },
      include: {
        allocations: true,
        stabilizationFunds: true,
      },
      orderBy: { createdAt: 'desc' },
    });
  }

  /**
   * Get SDR alternative by ID
   */
  async getSdrAlternative(sdrId: string) {
    return await prisma.gru_sdr_alternatives.findUnique({
      where: { sdrId },
    });
  }

  /**
   * Get all active SDR alternatives
   */
  async getAllSdrAlternatives() {
    return await prisma.gru_sdr_alternatives.findMany({
      where: { status: 'active' },
      orderBy: { effectiveDate: 'desc' },
    });
  }
}

export const gruSupranationalService = new GruSupranationalService();

