//! Price Oracle
//!
//! Aggregates SYNOR/USD prices from multiple sources with TWAP for stability.
//! Includes advanced features:
//! - Decentralized oracle network (Chainlink-style)
//! - Circuit breakers for flash crash protection
//! - Cross-chain price feeds via IBC
//! - ML-based anomaly detection
//! - Liquidation oracles for DeFi lending
//! - Options/Futures pricing (Black-Scholes)

mod anomaly;
mod circuit_breaker;
mod cross_chain;
mod decentralized;
mod derivatives;
mod liquidation;
mod price_feed;
mod twap;

pub use anomaly::{
    Anomaly, AnomalyData, AnomalyDetector, AnomalyDetectorConfig, AnomalyStats, AnomalyType,
    MarketDataPoint, RecommendedAction,
};
pub use circuit_breaker::{
    CircuitBreakerConfig, CircuitBreakerManager, CircuitBreakerStats, CircuitEvent, CircuitState,
    TriggerReason,
};
pub use cross_chain::{
    ChainNetwork, ChainPriceFetcher, CosmosPriceFetcher, CrossChainConfig, CrossChainOracle,
    CrossChainOracleBuilder, CrossChainOracleFactory, CrossChainPricePacket,
    CrossChainProductionConfig, EthereumPriceFetcher, IBCPriceRequest, MerkleProof,
};
pub use decentralized::{
    AggregationRound, AggregationStrategy, DecentralizedOracle, DecentralizedOracleConfig,
    OracleNode, PriceSubmission,
};
pub use derivatives::{
    BlackScholes, DerivativesOracle, FuturesContract, FuturesModel, FuturesPricing, OptionContract,
    OptionGreeks, OptionPricing, OptionStyle, OptionType, PerpetualModel, PerpetualPricing,
    VolatilitySurface,
};
pub use liquidation::{
    CollateralAsset, HealthStatus, LendingPosition, LiquidationCalculation, LiquidationEvent,
    LiquidationOracle, LiquidationOracleConfig, LiquidationPrice, LiquidationStats,
};
pub use price_feed::{
    AggregateFeed, ChainlinkFeed, CoinGeckoFeed, InternalDexFeed, MockPriceFeed, PriceFeed,
    PriceSource,
};
pub use twap::{
    OnChainTwap, OnChainTwapFactory, TwapCalculator, TwapConfig, TwapObservation,
    TwapOracleBuilder,
};

use crate::error::{EconomicsError, Result};
use crate::SynorDecimal;
use chrono::{DateTime, Duration, Utc};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Token price data point
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenPrice {
    /// Token symbol (e.g., "SYNOR")
    pub token: String,
    /// Quote currency (e.g., "USD")
    pub quote: String,
    /// Price value
    pub price: SynorDecimal,
    /// Timestamp
    pub timestamp: DateTime<Utc>,
    /// Source of the price
    pub source: PriceSource,
    /// Confidence score (0.0 - 1.0)
    pub confidence: f64,
}

impl TokenPrice {
    /// Create a new token price
    pub fn new(
        token: impl Into<String>,
        quote: impl Into<String>,
        price: SynorDecimal,
        source: PriceSource,
    ) -> Self {
        Self {
            token: token.into(),
            quote: quote.into(),
            price,
            timestamp: Utc::now(),
            source,
            confidence: 1.0,
        }
    }

    /// Check if the price is stale (older than max_age)
    pub fn is_stale(&self, max_age: Duration) -> bool {
        Utc::now() - self.timestamp > max_age
    }

    /// Get price pair key
    pub fn pair_key(&self) -> String {
        format!("{}/{}", self.token, self.quote)
    }
}

/// Oracle configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OracleConfig {
    /// Maximum price age before considered stale
    pub max_price_age: Duration,
    /// TWAP window duration
    pub twap_window: Duration,
    /// Minimum number of price sources required
    pub min_sources: usize,
    /// Maximum deviation between sources (percentage)
    pub max_deviation: Decimal,
    /// Update interval
    pub update_interval: Duration,
}

impl Default for OracleConfig {
    fn default() -> Self {
        Self {
            max_price_age: Duration::minutes(5),
            twap_window: Duration::hours(1),
            min_sources: 2,
            max_deviation: Decimal::new(5, 2), // 5%
            update_interval: Duration::seconds(30),
        }
    }
}

/// Aggregated price with metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AggregatedPrice {
    /// Spot price (latest)
    pub spot: SynorDecimal,
    /// Time-weighted average price
    pub twap: SynorDecimal,
    /// Median price across sources
    pub median: SynorDecimal,
    /// Number of active sources
    pub source_count: usize,
    /// Last update timestamp
    pub last_update: DateTime<Utc>,
    /// Overall confidence score
    pub confidence: f64,
}

/// Price oracle aggregating multiple sources
pub struct PriceOracle {
    config: OracleConfig,
    /// Price history keyed by pair (e.g., "SYNOR/USD")
    price_history: HashMap<String, Vec<TokenPrice>>,
    /// Active price feeds
    feeds: Vec<Box<dyn PriceFeed + Send + Sync>>,
    /// TWAP calculator
    twap_calculator: TwapCalculator,
    /// Cached aggregated prices
    cache: HashMap<String, AggregatedPrice>,
}

impl PriceOracle {
    /// Create a new price oracle
    pub fn new() -> Self {
        Self::with_config(OracleConfig::default())
    }

    /// Create with custom configuration
    pub fn with_config(config: OracleConfig) -> Self {
        let twap_calculator = TwapCalculator::new(config.twap_window);
        Self {
            config,
            price_history: HashMap::new(),
            feeds: Vec::new(),
            twap_calculator,
            cache: HashMap::new(),
        }
    }

    /// Add a price feed source
    pub fn add_feed(&mut self, feed: Box<dyn PriceFeed + Send + Sync>) {
        self.feeds.push(feed);
    }

    /// Update price from external source
    pub fn update_price(&mut self, price: TokenPrice) -> Result<()> {
        // Validate price
        if price.price <= Decimal::ZERO {
            return Err(EconomicsError::InvalidPrice(
                "Price must be positive".to_string(),
            ));
        }

        let pair_key = price.pair_key();

        // Add to history
        let history = self.price_history.entry(pair_key.clone()).or_default();
        history.push(price.clone());

        // Keep only prices within TWAP window
        let cutoff = Utc::now() - self.config.twap_window;
        history.retain(|p| p.timestamp > cutoff);

        // Update aggregated price
        self.update_aggregated_price(&pair_key)?;

        Ok(())
    }

    /// Get current spot price
    pub fn get_price(&self, token: &str, quote: &str) -> Result<SynorDecimal> {
        let pair_key = format!("{}/{}", token, quote);
        self.cache
            .get(&pair_key)
            .map(|p| p.spot)
            .ok_or_else(|| EconomicsError::PriceFeedUnavailable(pair_key))
    }

    /// Get TWAP price
    pub fn get_twap(&self, token: &str, quote: &str) -> Result<SynorDecimal> {
        let pair_key = format!("{}/{}", token, quote);
        self.cache
            .get(&pair_key)
            .map(|p| p.twap)
            .ok_or_else(|| EconomicsError::PriceFeedUnavailable(pair_key))
    }

    /// Get full aggregated price data
    pub fn get_aggregated_price(&self, token: &str, quote: &str) -> Result<AggregatedPrice> {
        let pair_key = format!("{}/{}", token, quote);
        self.cache
            .get(&pair_key)
            .cloned()
            .ok_or_else(|| EconomicsError::PriceFeedUnavailable(pair_key))
    }

    /// Get price history for a pair
    pub fn get_price_history(&self, token: &str, quote: &str) -> Vec<TokenPrice> {
        let pair_key = format!("{}/{}", token, quote);
        self.price_history.get(&pair_key).cloned().unwrap_or_default()
    }

    /// Fetch prices from all configured feeds
    pub async fn fetch_all_prices(&mut self) -> Result<()> {
        let mut new_prices = Vec::new();

        for feed in &self.feeds {
            match feed.fetch_price("SYNOR", "USD").await {
                Ok(price) => new_prices.push(price),
                Err(e) => {
                    tracing::warn!("Failed to fetch price from {}: {}", feed.source(), e);
                }
            }
        }

        if new_prices.len() < self.config.min_sources {
            return Err(EconomicsError::PriceFeedUnavailable(format!(
                "Only {} sources available, need at least {}",
                new_prices.len(),
                self.config.min_sources
            )));
        }

        // Check for excessive deviation
        let prices: Vec<_> = new_prices.iter().map(|p| p.price).collect();
        if !self.validate_price_consistency(&prices)? {
            return Err(EconomicsError::InvalidPrice(
                "Excessive price deviation between sources".to_string(),
            ));
        }

        // Update all prices
        for price in new_prices {
            self.update_price(price)?;
        }

        Ok(())
    }

    /// Validate that prices from different sources are consistent
    fn validate_price_consistency(&self, prices: &[SynorDecimal]) -> Result<bool> {
        if prices.len() < 2 {
            return Ok(true);
        }

        // Calculate median
        let mut sorted = prices.to_vec();
        sorted.sort();
        let median = sorted[sorted.len() / 2];

        // Check each price against median
        for price in prices {
            let deviation = (*price - median).abs() / median;
            if deviation > self.config.max_deviation {
                return Ok(false);
            }
        }

        Ok(true)
    }

    /// Update aggregated price for a pair
    fn update_aggregated_price(&mut self, pair_key: &str) -> Result<()> {
        let history = match self.price_history.get(pair_key) {
            Some(h) if !h.is_empty() => h,
            _ => return Ok(()),
        };

        // Get latest price (spot)
        let latest = history.last().unwrap();

        // Check if stale
        if latest.is_stale(self.config.max_price_age) {
            // Don't update cache with stale price, but don't error
            return Ok(());
        }

        // Calculate TWAP
        let twap = self.twap_calculator.calculate(history);

        // Calculate median
        let mut prices: Vec<_> = history.iter().map(|p| p.price).collect();
        prices.sort();
        let median = prices[prices.len() / 2];

        // Count unique sources
        let mut sources: std::collections::HashSet<String> = std::collections::HashSet::new();
        for p in history {
            sources.insert(format!("{:?}", p.source));
        }

        // Calculate confidence based on source diversity and freshness
        let freshness = 1.0
            - (Utc::now() - latest.timestamp).num_seconds() as f64
                / self.config.max_price_age.num_seconds() as f64;
        let diversity = (sources.len() as f64 / self.config.min_sources as f64).min(1.0);
        let confidence = (freshness * 0.6 + diversity * 0.4) * latest.confidence;

        let aggregated = AggregatedPrice {
            spot: latest.price,
            twap,
            median,
            source_count: sources.len(),
            last_update: latest.timestamp,
            confidence: confidence.max(0.0).min(1.0),
        };

        self.cache.insert(pair_key.to_string(), aggregated);

        Ok(())
    }

    /// Convert amount from one currency to another
    pub fn convert(
        &self,
        amount: SynorDecimal,
        from: &str,
        to: &str,
        use_twap: bool,
    ) -> Result<SynorDecimal> {
        if from == to {
            return Ok(amount);
        }

        // Try direct conversion
        let price = if use_twap {
            self.get_twap(from, to).or_else(|_| {
                // Try inverse
                self.get_twap(to, from).map(|p| Decimal::ONE / p)
            })
        } else {
            self.get_price(from, to).or_else(|_| {
                // Try inverse
                self.get_price(to, from).map(|p| Decimal::ONE / p)
            })
        }?;

        Ok(amount * price)
    }

    /// Get oracle health status
    pub fn health_check(&self) -> OracleHealthStatus {
        let mut pairs_status = HashMap::new();

        for (pair, prices) in &self.price_history {
            let latest = prices.last();
            let is_stale = latest
                .map(|p| p.is_stale(self.config.max_price_age))
                .unwrap_or(true);

            pairs_status.insert(
                pair.clone(),
                PairStatus {
                    price_count: prices.len(),
                    is_stale,
                    last_update: latest.map(|p| p.timestamp),
                },
            );
        }

        let healthy = !pairs_status.is_empty()
            && pairs_status.values().all(|s| !s.is_stale && s.price_count > 0);

        OracleHealthStatus {
            healthy,
            feed_count: self.feeds.len(),
            pairs: pairs_status,
        }
    }
}

impl Default for PriceOracle {
    fn default() -> Self {
        Self::new()
    }
}

/// Builder for creating a fully-configured PriceOracle
pub struct PriceOracleBuilder {
    config: OracleConfig,
    feeds: Vec<Box<dyn PriceFeed + Send + Sync>>,
}

impl PriceOracleBuilder {
    /// Create a new builder with default config
    pub fn new() -> Self {
        Self {
            config: OracleConfig::default(),
            feeds: Vec::new(),
        }
    }

    /// Set custom configuration
    pub fn with_config(mut self, config: OracleConfig) -> Self {
        self.config = config;
        self
    }

    /// Add a mock price feed (for testing)
    pub fn with_mock_feed(mut self, base_price: SynorDecimal) -> Self {
        use price_feed::MockPriceFeed;
        self.feeds.push(Box::new(MockPriceFeed::new(PriceSource::Internal, base_price)));
        self
    }

    /// Add the internal DEX price feed
    pub fn with_internal_dex(mut self, endpoint: impl Into<String>) -> Self {
        use price_feed::InternalDexFeed;
        self.feeds.push(Box::new(InternalDexFeed::new(endpoint)));
        self
    }

    /// Add Chainlink oracle feed
    pub fn with_chainlink(mut self, contract_address: impl Into<String>, rpc_url: impl Into<String>) -> Self {
        use price_feed::ChainlinkFeed;
        self.feeds.push(Box::new(ChainlinkFeed::new(contract_address, rpc_url)));
        self
    }

    /// Add CoinGecko API feed
    pub fn with_coingecko(mut self, api_key: Option<String>) -> Self {
        use price_feed::CoinGeckoFeed;
        self.feeds.push(Box::new(CoinGeckoFeed::new(api_key)));
        self
    }

    /// Add an aggregate feed that combines multiple sources
    pub fn with_aggregate_feed(mut self, feeds: Vec<Box<dyn PriceFeed + Send + Sync>>) -> Self {
        use price_feed::AggregateFeed;
        self.feeds.push(Box::new(AggregateFeed::new(feeds)));
        self
    }

    /// Add a custom price feed
    pub fn with_custom_feed(mut self, feed: Box<dyn PriceFeed + Send + Sync>) -> Self {
        self.feeds.push(feed);
        self
    }

    /// Build the configured PriceOracle
    pub fn build(self) -> PriceOracle {
        let mut oracle = PriceOracle::with_config(self.config);
        for feed in self.feeds {
            oracle.add_feed(feed);
        }
        oracle
    }
}

impl Default for PriceOracleBuilder {
    fn default() -> Self {
        Self::new()
    }
}

/// Factory for creating pre-configured price oracles
pub struct OracleFactory;

impl OracleFactory {
    /// Create a development/testing oracle with mock feeds
    pub fn development(base_price: SynorDecimal) -> PriceOracle {
        use price_feed::MockPriceFeed;

        let mut oracle = PriceOracle::new();

        // Add multiple mock feeds with slight variations for testing
        oracle.add_feed(Box::new(MockPriceFeed::new(PriceSource::Internal, base_price)));
        oracle.add_feed(Box::new(MockPriceFeed::new(PriceSource::SynorDex, base_price)));

        oracle
    }

    /// Create a production oracle with all available feeds
    pub fn production(config: ProductionOracleConfig) -> PriceOracle {
        use price_feed::{ChainlinkFeed, CoinGeckoFeed, InternalDexFeed};

        let mut oracle = PriceOracle::with_config(config.oracle_config);

        // Add internal DEX feed
        if let Some(dex_endpoint) = config.dex_endpoint {
            oracle.add_feed(Box::new(InternalDexFeed::new(dex_endpoint)));
        }

        // Add Chainlink feed
        if let Some((contract, rpc)) = config.chainlink {
            oracle.add_feed(Box::new(ChainlinkFeed::new(contract, rpc)));
        }

        // Add CoinGecko feed
        if config.coingecko_enabled {
            oracle.add_feed(Box::new(CoinGeckoFeed::new(config.coingecko_api_key)));
        }

        oracle
    }

    /// Create an aggregate oracle that combines multiple feeds for resilience
    pub fn aggregate(feeds: Vec<Box<dyn PriceFeed + Send + Sync>>) -> PriceOracle {
        use price_feed::AggregateFeed;

        let mut oracle = PriceOracle::new();
        oracle.add_feed(Box::new(AggregateFeed::new(feeds)));
        oracle
    }
}

/// Configuration for production oracle
#[derive(Debug, Clone, Default)]
pub struct ProductionOracleConfig {
    /// Base oracle configuration
    pub oracle_config: OracleConfig,
    /// Internal DEX endpoint URL
    pub dex_endpoint: Option<String>,
    /// Chainlink contract address and RPC URL
    pub chainlink: Option<(String, String)>,
    /// Enable CoinGecko feed
    pub coingecko_enabled: bool,
    /// CoinGecko API key (optional, for higher rate limits)
    pub coingecko_api_key: Option<String>,
}

/// Status of a trading pair
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PairStatus {
    pub price_count: usize,
    pub is_stale: bool,
    pub last_update: Option<DateTime<Utc>>,
}

/// Oracle health status
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OracleHealthStatus {
    pub healthy: bool,
    pub feed_count: usize,
    pub pairs: HashMap<String, PairStatus>,
}

#[cfg(test)]
mod tests {
    use super::*;
    use rust_decimal_macros::dec;

    #[test]
    fn test_update_and_get_price() {
        let mut oracle = PriceOracle::new();

        let price = TokenPrice::new("SYNOR", "USD", dec!(1.50), PriceSource::Internal);
        oracle.update_price(price).unwrap();

        let retrieved = oracle.get_price("SYNOR", "USD").unwrap();
        assert_eq!(retrieved, dec!(1.50));
    }

    #[test]
    fn test_twap_calculation() {
        let mut oracle = PriceOracle::new();

        // Add multiple prices
        for i in 0..5 {
            let price = TokenPrice {
                token: "SYNOR".to_string(),
                quote: "USD".to_string(),
                price: dec!(1.0) + Decimal::new(i, 1),
                timestamp: Utc::now() - Duration::minutes(5 - i as i64),
                source: PriceSource::Internal,
                confidence: 1.0,
            };
            oracle.update_price(price).unwrap();
        }

        let twap = oracle.get_twap("SYNOR", "USD").unwrap();
        // TWAP should be weighted average
        assert!(twap > dec!(1.0) && twap < dec!(1.5));
    }

    #[test]
    fn test_price_conversion() {
        let mut oracle = PriceOracle::new();

        let price = TokenPrice::new("SYNOR", "USD", dec!(2.0), PriceSource::Internal);
        oracle.update_price(price).unwrap();

        // Convert 10 SYNOR to USD
        let usd = oracle.convert(dec!(10), "SYNOR", "USD", false).unwrap();
        assert_eq!(usd, dec!(20));

        // Convert 20 USD to SYNOR (inverse)
        let synor = oracle.convert(dec!(20), "USD", "SYNOR", false).unwrap();
        assert_eq!(synor, dec!(10));
    }

    #[test]
    fn test_health_check() {
        let oracle = PriceOracle::new();
        let status = oracle.health_check();
        assert!(!status.healthy); // No prices yet
    }
}
