// GPN Layer 2: Global Switching Layer
// DBIS switch routes payments using FX cost optimization, liquidity availability, SRI-based risk weighting

import { Decimal } from '@prisma/client/runtime/library';
import { v4 as uuidv4 } from 'uuid';
import { logger } from '@/infrastructure/monitoring/logger';
import { sireRoutingService } from '@/core/settlement/sire/sire-routing.service';
import prisma from '@/shared/database/prisma';

export interface GpnRoutingRequest {
  sourceBankId: string;
  destinationBankId: string;
  amount: string;
  currencyCode: string;
  assetType: string;
  paymentType: string;
}

export interface GpnRoute {
  routeId: string;
  routePath: string[];
  fxCost: Decimal;
  liquidityScore: Decimal;
  sriWeight: Decimal;
  totalCost: Decimal;
}

export class GpnSwitchingService {
  /**
   * Route payment through DBIS switch
   */
  async routePayment(request: GpnRoutingRequest): Promise<GpnRoute> {
    const routeId = `GPN-ROUTE-${uuidv4()}`;

    // Get all possible routes
    const possibleRoutes = await this.getPossibleRoutes(
      request.sourceBankId,
      request.destinationBankId,
      request.currencyCode
    );

    if (possibleRoutes.length === 0) {
      throw new Error('No routes found for payment');
    }

    // Calculate route costs with optimization factors
    const routeCosts = await Promise.all(
      possibleRoutes.map(async (route) => {
        const fxCost = await this.calculateFxCost(route, request.amount, request.currencyCode);
        const liquidityScore = await this.calculateLiquidityScore(route);
        const sriWeight = await this.calculateSriWeight(route);

        // Total cost: FX cost + liquidity penalty + SRI risk
        const liquidityPenalty = new Decimal(100).minus(liquidityScore).div(100);
        const totalCost = fxCost.plus(liquidityPenalty).plus(sriWeight);

        return {
          routeId: route.routeId,
          routePath: route.routePath,
          fxCost,
          liquidityScore,
          sriWeight,
          totalCost,
        };
      })
    );

    // Find optimal route (minimum total cost)
    const optimalRoute = routeCosts.reduce((min, current) =>
      current.totalCost.lt(min.totalCost) ? current : min
    );

    // Save route to database
    await prisma.gpnRoute.create({
      data: {
        routeId: optimalRoute.routeId,
        sourceBankId: request.sourceBankId,
        destinationBankId: request.destinationBankId,
        currencyCode: request.currencyCode,
        routePath: optimalRoute.routePath,
        fxCost: optimalRoute.fxCost,
        liquidityScore: optimalRoute.liquidityScore,
        sriWeight: optimalRoute.sriWeight,
        totalCost: optimalRoute.totalCost,
        status: 'active',
      },
    });

    logger.info(`GPN Layer 2: Routed payment via optimal route`, {
      routeId: optimalRoute.routeId,
      totalCost: optimalRoute.totalCost.toString(),
    });

    return optimalRoute;
  }

  /**
   * Get possible routes between source and destination
   */
  private async getPossibleRoutes(
    sourceBankId: string,
    destinationBankId: string,
    currencyCode: string
  ): Promise<Array<{ routeId: string; routePath: string[] }>> {
    // Direct route
    const directRoute = {
      routeId: `DIRECT-${sourceBankId}-${destinationBankId}`,
      routePath: [sourceBankId, destinationBankId],
    };

    // Route via DBIS
    const viaDbisRoute = {
      routeId: `VIA-DBIS-${sourceBankId}-${destinationBankId}`,
      routePath: [sourceBankId, 'DBIS', destinationBankId],
    };

    // Check for intermediate SCB routes
    const intermediateRoutes = await this.findIntermediateRoutes(
      sourceBankId,
      destinationBankId,
      currencyCode
    );

    return [directRoute, viaDbisRoute, ...intermediateRoutes];
  }

  /**
   * Find intermediate SCB routes
   */
  private async findIntermediateRoutes(
    sourceBankId: string,
    destinationBankId: string,
    currencyCode: string
  ): Promise<Array<{ routeId: string; routePath: string[] }>> {
    // Get all active SCBs that support the currency
    const intermediateBanks = await prisma.sovereignBank.findMany({
      where: {
        status: 'active',
        id: {
          notIn: [sourceBankId, destinationBankId],
        },
      },
      take: 5, // Limit to 5 intermediate options
    });

    return intermediateBanks.map((bank) => ({
      routeId: `VIA-${bank.id}-${sourceBankId}-${destinationBankId}`,
      routePath: [sourceBankId, bank.id, destinationBankId],
    }));
  }

  /**
   * Calculate FX cost for route
   */
  private async calculateFxCost(
    route: { routeId: string; routePath: string[] },
    amount: string,
    currencyCode: string
  ): Promise<Decimal> {
    // Use SIRE for FX cost calculation if available
    if (route.routePath.length === 2) {
      try {
        const sireRoute = await sireRoutingService.calculateOptimalRoute({
          sourceBankId: route.routePath[0],
          destinationBankId: route.routePath[1],
          amount,
          currencyCode,
        });
        // SIRE already includes FX cost in its calculation
        // For now, return a simplified FX cost estimate
      } catch (error) {
        logger.warn('SIRE routing unavailable, using default FX cost', { error });
      }
    }

    // Default FX cost calculation (0.1% for direct, 0.2% for intermediate)
    const fxRate = route.routePath.length === 2 ? 0.001 : 0.002;
    return new Decimal(amount).times(fxRate);
  }

  /**
   * Calculate liquidity score (0-100, higher is better)
   */
  private async calculateLiquidityScore(
    route: { routeId: string; routePath: string[] }
  ): Promise<Decimal> {
    // Check liquidity pools for each node in route
    // Simplified: assume 80% liquidity score for direct routes, 70% for intermediate
    const baseScore = route.routePath.length === 2 ? 80 : 70;

    // In production, this would query actual liquidity pools
    return new Decimal(baseScore);
  }

  /**
   * Calculate SRI-based risk weight
   */
  private async calculateSriWeight(
    route: { routeId: string; routePath: string[] }
  ): Promise<Decimal> {
    // Get SRI scores for each bank in route
    // In production, this would query SRI calculator
    // For now, use default risk weight
    const defaultRiskWeight = 0.05; // 5% risk weight

    return new Decimal(defaultRiskWeight);
  }
}

export const gpnSwitchingService = new GpnSwitchingService();

