/**
 * Liquidity Engine Service
 * Intelligent routing and liquidity management with decision logic
 */

import { ethers } from 'ethers';

export enum SwapProvider {
  UniswapV3 = 0,
  Curve = 1,
  Dodoex = 2,
  Balancer = 3,
  OneInch = 4,
}

export enum SwapSize {
  Small = 0,   // < $10k
  Medium = 1, // $10k - $100k
  Large = 2,  // > $100k
}

export interface Quote {
  provider: SwapProvider;
  amountOut: bigint;
  slippage: number;
  gasEstimate: bigint;
  confidence: number; // 0-100
  route: string[];
}

export interface RoutingDecision {
  provider: SwapProvider;
  route: string[];
  expectedOutput: bigint;
  slippage: number;
  gasEstimate: bigint;
  confidence: number;
  reasoning: string;
}

export interface LiquidityDecisionMap {
  sizeThresholds: {
    small: { max: number; providers: SwapProvider[] };
    medium: { max: number; providers: SwapProvider[] };
    large: { providers: SwapProvider[] };
  };
  slippageRules: {
    lowSlippage: { max: number; prefer: SwapProvider };
    mediumSlippage: { max: number; prefer: SwapProvider };
    highSlippage: { prefer: SwapProvider };
  };
  liquidityRules: {
    highLiquidity: { min: number; prefer: SwapProvider };
    mediumLiquidity: { prefer: SwapProvider };
    lowLiquidity: { prefer: SwapProvider };
  };
  timeRules: {
    highVolatility: { prefer: SwapProvider };
    normal: { prefer: SwapProvider };
  };
}

export class LiquidityEngine {
  private provider: ethers.Provider;
  private enhancedSwapRouter: ethers.Contract;
  private decisionMap: LiquidityDecisionMap;

  constructor(
    provider: ethers.Provider,
    enhancedSwapRouterAddress: string,
    decisionMap?: LiquidityDecisionMap
  ) {
    this.provider = provider;
    this.enhancedSwapRouter = new ethers.Contract(
      enhancedSwapRouterAddress,
      [], // ABI would go here
      provider
    );

    // Default decision map
    this.decisionMap = decisionMap || this.getDefaultDecisionMap();
  }

  /**
   * Find best route for a swap
   */
  async findBestRoute(
    inputToken: string,
    outputToken: string,
    amount: bigint,
    maxSlippage: number = 0.5
  ): Promise<RoutingDecision> {
    // 1. Get quotes from all providers
    const quotes = await this.getQuotes(inputToken, outputToken, amount);

    if (quotes.length === 0) {
      throw new Error('No quotes available');
    }

    // 2. Determine swap size category
    const sizeCategory = this.getSwapSize(amount);

    // 3. Apply decision logic
    const decision = this.applyDecisionLogic(quotes, sizeCategory, maxSlippage);

    return decision;
  }

  /**
   * Get quotes from all enabled providers
   */
  async getQuotes(
    inputToken: string,
    outputToken: string,
    amount: bigint
  ): Promise<Quote[]> {
    try {
      const result = await this.enhancedSwapRouter.getQuotes(outputToken, amount);
      const providers = result[0] as SwapProvider[];
      const amounts = result[1] as bigint[];

      const quotes: Quote[] = [];
      for (let i = 0; i < providers.length; i++) {
        if (amounts[i] > 0n) {
          const slippage = this.calculateSlippage(amount, amounts[i]);
          const gasEstimate = await this.estimateGas(providers[i], amount);
          const confidence = this.calculateConfidence(providers[i], slippage, gasEstimate);

          quotes.push({
            provider: providers[i],
            amountOut: amounts[i],
            slippage,
            gasEstimate,
            confidence,
            route: [inputToken, outputToken], // Simplified, would be actual route
          });
        }
      }

      return quotes.sort((a, b) => {
        // Sort by best effective output (considering gas)
        const aEffective = a.amountOut - a.gasEstimate;
        const bEffective = b.amountOut - b.gasEstimate;
        return aEffective > bEffective ? -1 : 1;
      });
    } catch (error) {
      console.error('Error getting quotes:', error);
      return [];
    }
  }

  /**
   * Apply decision logic to select best route
   */
  applyDecisionLogic(
    quotes: Quote[],
    sizeCategory: SwapSize,
    maxSlippage: number
  ): RoutingDecision {
    // Filter by size-based preferences
    const preferredProviders = this.getPreferredProviders(sizeCategory);
    const filteredQuotes = quotes.filter(q => preferredProviders.includes(q.provider));

    // Filter by slippage rules
    const slippageFiltered = this.applySlippageRules(filteredQuotes, maxSlippage);

    // Select best quote
    const bestQuote = slippageFiltered[0] || quotes[0];

    return {
      provider: bestQuote.provider,
      route: bestQuote.route,
      expectedOutput: bestQuote.amountOut,
      slippage: bestQuote.slippage,
      gasEstimate: bestQuote.gasEstimate,
      confidence: bestQuote.confidence,
      reasoning: this.generateReasoning(bestQuote, sizeCategory),
    };
  }

  /**
   * Get preferred providers for swap size
   */
  getPreferredProviders(sizeCategory: SwapSize): SwapProvider[] {
    switch (sizeCategory) {
      case SwapSize.Small:
        return this.decisionMap.sizeThresholds.small.providers;
      case SwapSize.Medium:
        return this.decisionMap.sizeThresholds.medium.providers;
      case SwapSize.Large:
        return this.decisionMap.sizeThresholds.large.providers;
    }
  }

  /**
   * Apply slippage-based routing rules
   */
  applySlippageRules(quotes: Quote[], maxSlippage: number): Quote[] {
    if (maxSlippage <= this.decisionMap.slippageRules.lowSlippage.max) {
      // Prefer low slippage provider
      return quotes.filter(q => q.slippage <= this.decisionMap.slippageRules.lowSlippage.max)
        .sort((a, b) => a.slippage - b.slippage);
    } else if (maxSlippage <= this.decisionMap.slippageRules.mediumSlippage.max) {
      return quotes.filter(q => q.slippage <= this.decisionMap.slippageRules.mediumSlippage.max);
    } else {
      // High slippage - use preferred provider
      return quotes.filter(q => q.provider === this.decisionMap.slippageRules.highSlippage.prefer);
    }
  }

  /**
   * Determine swap size category
   */
  getSwapSize(amount: bigint): SwapSize {
    // Assuming 1 ETH = $2000 for size calculation
    const usdValue = Number(amount) * 2000 / 1e18;
    
    if (usdValue < 10000) return SwapSize.Small;
    if (usdValue < 100000) return SwapSize.Medium;
    return SwapSize.Large;
  }

  /**
   * Calculate slippage percentage
   */
  calculateSlippage(amountIn: bigint, amountOut: bigint): number {
    // Simplified - would use actual price oracle
    const expectedOut = amountIn; // 1:1 for stablecoins
    const slippage = Number(amountOut - expectedOut) / Number(expectedOut) * 100;
    return Math.abs(slippage);
  }

  /**
   * Estimate gas for swap
   */
  async estimateGas(provider: SwapProvider, amount: bigint): Promise<bigint> {
    // Gas estimates per provider (would be dynamic in production)
    const gasEstimates: Record<SwapProvider, bigint> = {
      [SwapProvider.UniswapV3]: 150000n,
      [SwapProvider.Curve]: 200000n,
      [SwapProvider.Dodoex]: 180000n,
      [SwapProvider.Balancer]: 220000n,
      [SwapProvider.OneInch]: 250000n,
    };

    return gasEstimates[provider] || 200000n;
  }

  /**
   * Calculate confidence score (0-100)
   */
  calculateConfidence(
    provider: SwapProvider,
    slippage: number,
    gasEstimate: bigint
  ): number {
    let confidence = 100;

    // Reduce confidence based on slippage
    if (slippage > 1) confidence -= 20;
    else if (slippage > 0.5) confidence -= 10;

    // Reduce confidence based on gas (higher gas = lower confidence)
    const gasPenalty = Number(gasEstimate) > 200000 ? 10 : 0;
    confidence -= gasPenalty;

    // Provider-specific adjustments
    if (provider === SwapProvider.Dodoex) confidence += 5; // PMM is more reliable
    if (provider === SwapProvider.OneInch) confidence -= 5; // Aggregation can be less reliable

    return Math.max(0, Math.min(100, confidence));
  }

  /**
   * Generate reasoning for decision
   */
  generateReasoning(quote: Quote, sizeCategory: SwapSize): string {
    const sizeStr = SwapSize[sizeCategory];
    const providerStr = SwapProvider[quote.provider];
    
    return `Selected ${providerStr} for ${sizeStr} swap. ` +
           `Expected output: ${quote.amountOut.toString()}, ` +
           `Slippage: ${quote.slippage.toFixed(2)}%, ` +
           `Confidence: ${quote.confidence}%`;
  }

  /**
   * Get default decision map
   */
  getDefaultDecisionMap(): LiquidityDecisionMap {
    return {
      sizeThresholds: {
        small: {
          max: 10000,
          providers: [SwapProvider.UniswapV3, SwapProvider.Dodoex],
        },
        medium: {
          max: 100000,
          providers: [SwapProvider.Dodoex, SwapProvider.Balancer, SwapProvider.UniswapV3],
        },
        large: {
          providers: [SwapProvider.Dodoex, SwapProvider.Curve, SwapProvider.Balancer],
        },
      },
      slippageRules: {
        lowSlippage: { max: 0.1, prefer: SwapProvider.Dodoex },
        mediumSlippage: { max: 0.5, prefer: SwapProvider.Balancer },
        highSlippage: { prefer: SwapProvider.Curve },
      },
      liquidityRules: {
        highLiquidity: { min: 1000000, prefer: SwapProvider.UniswapV3 },
        mediumLiquidity: { prefer: SwapProvider.Dodoex },
        lowLiquidity: { prefer: SwapProvider.Curve },
      },
      timeRules: {
        highVolatility: { prefer: SwapProvider.Dodoex },
        normal: { prefer: SwapProvider.UniswapV3 },
      },
    };
  }

  /**
   * Update decision map
   */
  updateDecisionMap(newMap: Partial<LiquidityDecisionMap>): void {
    this.decisionMap = { ...this.decisionMap, ...newMap };
  }

  /**
   * Get current decision map
   */
  getDecisionMap(): LiquidityDecisionMap {
    return this.decisionMap;
  }
}

export default LiquidityEngine;

