/**
 * Exchange Rate Validator
 *
 * Implements dual-layer validation for dynamic pricing:
 * 1. System-level: Rate trustworthiness (multi-source deviation, volatility)
 * 2. User-level: Slippage protection (preview vs submit price change)
 *
 * @see Intent: blocklets/core/ai/intent/20260112-dynamic-price.md
 */

import type { ExchangeRateResult } from './types';
import logger from '../logger';

// System-level thresholds (non-bypassable)
const MULTI_SOURCE_DEVIATION_THRESHOLD = 0.1; // 10%
const VOLATILITY_30S_THRESHOLD = 0.2; // 20%
const VOLATILITY_WINDOW_MS = 30 * 1000; // 30 seconds

// Default user-level slippage threshold
export const DEFAULT_SLIPPAGE_PERCENT = 0.5; // 0.5%

export type RateValidationErrorCode = 'PRICE_UNAVAILABLE' | 'PRICE_UNSTABLE';

export interface RateValidationResult {
  trustworthy: boolean;
  error_code?: RateValidationErrorCode;
  error_message?: string;
  deviation_percent?: number;
  volatility_percent?: number;
}

export interface SlippageValidationResult {
  passed: boolean;
  change_percent: number;
  preview_rate: string;
  submit_rate: string;
  slippage_threshold: number;
}

interface RateHistoryEntry {
  rate: string;
  timestamp_ms: number;
}

// In-memory rate history for volatility calculation
const rateHistoryMap: Map<string, RateHistoryEntry[]> = new Map();
const RATE_HISTORY_MAX_SIZE = 100;

/**
 * Record a trusted rate for volatility tracking
 * Only rates that pass system-level validation should be recorded
 */
export function recordTrustedRate(symbol: string, rate: string, timestampMs: number): void {
  const key = symbol.toUpperCase();
  const history = rateHistoryMap.get(key) || [];

  history.push({ rate, timestamp_ms: timestampMs });

  // Keep history bounded
  while (history.length > RATE_HISTORY_MAX_SIZE) {
    history.shift();
  }

  rateHistoryMap.set(key, history);
}

/**
 * Get the previous trusted rate within the volatility window
 * rate_prev: The most recent rate that passed system-level validation
 */
function getPreviousTrustedRate(symbol: string, currentTimestampMs: number): RateHistoryEntry | null {
  const key = symbol.toUpperCase();
  const history = rateHistoryMap.get(key) || [];

  // Find the most recent rate within the 30s window
  const windowStart = currentTimestampMs - VOLATILITY_WINDOW_MS;

  for (let i = history.length - 1; i >= 0; i--) {
    const entry = history[i];
    if (entry && entry.timestamp_ms >= windowStart && entry.timestamp_ms < currentTimestampMs) {
      return entry;
    }
  }

  return null;
}

/**
 * System-level rate validation (non-bypassable)
 *
 * Validates:
 * 1. Data source availability - at least one provider returned a rate
 * 2. Multi-source deviation - if multiple sources, deviation ≤ 10%
 * 3. 30s volatility - rate change within 30s window ≤ 20%
 *
 * @param rateResult - The exchange rate result from providers
 * @param symbol - Token symbol for volatility tracking
 * @returns Validation result with error details if failed
 */
export function validateRateTrustworthy(rateResult: ExchangeRateResult, symbol: string): RateValidationResult {
  const providers = rateResult.providers || [];

  // 1. Check data source availability
  if (providers.length === 0) {
    logger.warn('Rate validation failed: No providers available', { symbol });
    return {
      trustworthy: false,
      error_code: 'PRICE_UNAVAILABLE',
      error_message: 'All exchange rate providers are unavailable',
    };
  }

  // 2. Check multi-source deviation (if multiple sources)
  if (providers.length >= 2) {
    const rates = providers.map((p) => parseFloat(p.rate)).filter((r) => Number.isFinite(r));
    if (rates.length >= 2) {
      const maxRate = Math.max(...rates);
      const minRate = Math.min(...rates);
      const median = parseFloat(rateResult.rate);

      if (median > 0) {
        const deviationPercent = (maxRate - minRate) / median;

        if (deviationPercent > MULTI_SOURCE_DEVIATION_THRESHOLD) {
          logger.warn('Rate validation failed: Multi-source deviation too high', {
            symbol,
            deviation: `${(deviationPercent * 100).toFixed(2)}%`,
            threshold: `${MULTI_SOURCE_DEVIATION_THRESHOLD * 100}%`,
            rates: providers.map((p) => ({ provider: p.provider_name, rate: p.rate })),
          });

          return {
            trustworthy: false,
            error_code: 'PRICE_UNSTABLE',
            error_message: `Multi-source deviation ${(deviationPercent * 100).toFixed(2)}% exceeds 10% threshold`,
            deviation_percent: deviationPercent * 100,
          };
        }
      }
    }
  }

  // 3. Check 30s volatility
  const prevEntry = getPreviousTrustedRate(symbol, rateResult.timestamp_ms);
  if (prevEntry) {
    const currentRate = parseFloat(rateResult.rate);
    const prevRate = parseFloat(prevEntry.rate);

    if (prevRate > 0 && Number.isFinite(currentRate) && Number.isFinite(prevRate)) {
      const volatilityPercent = Math.abs(currentRate - prevRate) / prevRate;

      if (volatilityPercent > VOLATILITY_30S_THRESHOLD) {
        logger.warn('Rate validation failed: 30s volatility too high', {
          symbol,
          volatility: `${(volatilityPercent * 100).toFixed(2)}%`,
          threshold: `${VOLATILITY_30S_THRESHOLD * 100}%`,
          currentRate: rateResult.rate,
          prevRate: prevEntry.rate,
          timeDiff: `${(rateResult.timestamp_ms - prevEntry.timestamp_ms) / 1000}s`,
        });

        return {
          trustworthy: false,
          error_code: 'PRICE_UNSTABLE',
          error_message: `30s volatility ${(volatilityPercent * 100).toFixed(2)}% exceeds 20% threshold`,
          volatility_percent: volatilityPercent * 100,
        };
      }
    }
  }

  // Rate is trustworthy - record it for future volatility calculations
  recordTrustedRate(symbol, rateResult.rate, rateResult.timestamp_ms);

  logger.debug('Rate validation passed', {
    symbol,
    rate: rateResult.rate,
    providers: providers.length,
  });

  return { trustworthy: true };
}

/**
 * User-level slippage validation (bypassable with user confirmation)
 *
 * Validates that the price change between preview and submit is within
 * the user's configured slippage tolerance.
 *
 * @param previewRate - Rate displayed during preview
 * @param submitRate - Rate at submit time
 * @param userSlippage - User's slippage tolerance (default 0.5%)
 * @param priceConfirmed - Whether user has confirmed the price change
 * @returns Validation result
 */
export function validateUserSlippage(
  previewRate: string,
  submitRate: string,
  userSlippage: number = DEFAULT_SLIPPAGE_PERCENT,
  priceConfirmed: boolean = false
): SlippageValidationResult {
  const previewNum = parseFloat(previewRate);
  const submitNum = parseFloat(submitRate);

  // Invalid rates - treat as passed (backend will handle separately)
  if (!Number.isFinite(previewNum) || !Number.isFinite(submitNum) || previewNum <= 0) {
    return {
      passed: true,
      change_percent: 0,
      preview_rate: previewRate,
      submit_rate: submitRate,
      slippage_threshold: userSlippage,
    };
  }

  const changePercent = (Math.abs(submitNum - previewNum) / previewNum) * 100;
  const slippageThreshold = userSlippage; // Already in percent

  // If user has confirmed, always pass
  if (priceConfirmed) {
    logger.info('Slippage check bypassed: User confirmed price change', {
      change: `${changePercent.toFixed(4)}%`,
      threshold: `${slippageThreshold}%`,
    });
    return {
      passed: true,
      change_percent: changePercent,
      preview_rate: previewRate,
      submit_rate: submitRate,
      slippage_threshold: slippageThreshold,
    };
  }

  const passed = changePercent <= slippageThreshold;

  if (!passed) {
    logger.info('Slippage check failed: Price change exceeds user threshold', {
      change: `${changePercent.toFixed(4)}%`,
      threshold: `${slippageThreshold}%`,
      preview_rate: previewRate,
      submit_rate: submitRate,
    });
  }

  return {
    passed,
    change_percent: changePercent,
    preview_rate: previewRate,
    submit_rate: submitRate,
    slippage_threshold: slippageThreshold,
  };
}

/**
 * Combined validation for Submit phase
 *
 * Executes both validations in order:
 * 1. System-level (non-bypassable)
 * 2. User-level (bypassable with confirmation)
 */
export interface SubmitValidationResult {
  passed: boolean;
  error_code?: 'PRICE_UNAVAILABLE' | 'PRICE_UNSTABLE' | 'PRICE_CHANGED';
  error_message?: string;
  system_validation: RateValidationResult;
  slippage_validation?: SlippageValidationResult;
}

export function validateForSubmit(
  rateResult: ExchangeRateResult,
  symbol: string,
  previewRate: string | undefined,
  userSlippage: number = DEFAULT_SLIPPAGE_PERCENT,
  priceConfirmed: boolean = false
): SubmitValidationResult {
  // 1. System-level validation (non-bypassable)
  const systemResult = validateRateTrustworthy(rateResult, symbol);
  if (!systemResult.trustworthy) {
    return {
      passed: false,
      error_code: systemResult.error_code,
      error_message: systemResult.error_message,
      system_validation: systemResult,
    };
  }

  // 2. User-level slippage validation (bypassable)
  if (previewRate) {
    const slippageResult = validateUserSlippage(previewRate, rateResult.rate, userSlippage, priceConfirmed);

    if (!slippageResult.passed) {
      return {
        passed: false,
        error_code: 'PRICE_CHANGED',
        error_message: `Price changed ${slippageResult.change_percent.toFixed(2)}% (threshold: ${slippageResult.slippage_threshold}%)`,
        system_validation: systemResult,
        slippage_validation: slippageResult,
      };
    }

    return {
      passed: true,
      system_validation: systemResult,
      slippage_validation: slippageResult,
    };
  }

  return {
    passed: true,
    system_validation: systemResult,
  };
}

/**
 * Clear rate history (for testing)
 */
export function clearRateHistory(): void {
  rateHistoryMap.clear();
}
