//! Time-Weighted Average Price (TWAP) Calculator
//!
//! TWAP provides price stability by averaging over a time window,
//! reducing impact of short-term manipulation or volatility.

use crate::oracle::TokenPrice;
use crate::SynorDecimal;
use chrono::{DateTime, Duration, Utc};
use rust_decimal::Decimal;

/// TWAP calculator configuration
#[derive(Debug, Clone)]
pub struct TwapConfig {
    /// Time window for TWAP calculation
    pub window: Duration,
    /// Minimum data points required
    pub min_data_points: usize,
    /// Whether to use volume weighting (when available)
    pub volume_weighted: bool,
}

impl Default for TwapConfig {
    fn default() -> Self {
        Self {
            window: Duration::hours(1),
            min_data_points: 3,
            volume_weighted: false,
        }
    }
}

/// TWAP calculator
pub struct TwapCalculator {
    config: TwapConfig,
}

impl TwapCalculator {
    /// Create a new TWAP calculator with specified window
    pub fn new(window: Duration) -> Self {
        Self {
            config: TwapConfig {
                window,
                ..Default::default()
            },
        }
    }

    /// Create with full configuration
    pub fn with_config(config: TwapConfig) -> Self {
        Self { config }
    }

    /// Calculate TWAP from price history
    ///
    /// Uses time-weighted interpolation between price points
    pub fn calculate(&self, prices: &[TokenPrice]) -> SynorDecimal {
        if prices.is_empty() {
            return Decimal::ZERO;
        }

        if prices.len() == 1 {
            return prices[0].price;
        }

        let now = Utc::now();
        let window_start = now - self.config.window;

        // Filter prices within window
        let relevant: Vec<_> = prices
            .iter()
            .filter(|p| p.timestamp >= window_start)
            .collect();

        if relevant.is_empty() {
            // Fall back to latest price if all data is stale
            return prices.last().map(|p| p.price).unwrap_or(Decimal::ZERO);
        }

        // Time-weighted average
        self.calculate_time_weighted(&relevant, window_start, now)
    }

    /// Calculate time-weighted average
    ///
    /// For each time interval between price points, the price is weighted
    /// by the duration of that interval.
    fn calculate_time_weighted(
        &self,
        prices: &[&TokenPrice],
        start: DateTime<Utc>,
        end: DateTime<Utc>,
    ) -> SynorDecimal {
        let total_duration = (end - start).num_seconds() as f64;
        if total_duration <= 0.0 {
            return prices.last().map(|p| p.price).unwrap_or(Decimal::ZERO);
        }

        let mut weighted_sum = Decimal::ZERO;
        let mut total_weight = Decimal::ZERO;

        for i in 0..prices.len() {
            let price = &prices[i];

            // Calculate time weight for this price point
            let interval_start = if i == 0 {
                start.max(price.timestamp)
            } else {
                prices[i - 1].timestamp.max(price.timestamp)
            };

            let interval_end = if i + 1 < prices.len() {
                prices[i + 1].timestamp
            } else {
                end
            };

            let duration = (interval_end - interval_start).num_seconds() as f64;
            if duration > 0.0 {
                let weight = Decimal::from_f64_retain(duration / total_duration)
                    .unwrap_or(Decimal::ZERO);

                weighted_sum += price.price * weight;
                total_weight += weight;
            }
        }

        if total_weight == Decimal::ZERO {
            prices.last().map(|p| p.price).unwrap_or(Decimal::ZERO)
        } else {
            weighted_sum / total_weight
        }
    }

    /// Calculate TWAP with exponential decay (more recent prices weighted higher)
    pub fn calculate_exponential(&self, prices: &[TokenPrice], decay_rate: f64) -> SynorDecimal {
        if prices.is_empty() {
            return Decimal::ZERO;
        }

        if prices.len() == 1 {
            return prices[0].price;
        }

        let now = Utc::now();
        let mut weighted_sum = Decimal::ZERO;
        let mut total_weight = Decimal::ZERO;

        for price in prices {
            let age_seconds = (now - price.timestamp).num_seconds() as f64;
            let weight = (-decay_rate * age_seconds).exp();
            let weight_decimal = Decimal::from_f64_retain(weight).unwrap_or(Decimal::ZERO);

            weighted_sum += price.price * weight_decimal;
            total_weight += weight_decimal;
        }

        if total_weight == Decimal::ZERO {
            prices.last().map(|p| p.price).unwrap_or(Decimal::ZERO)
        } else {
            weighted_sum / total_weight
        }
    }

    /// Calculate simple moving average (not time-weighted)
    pub fn calculate_sma(&self, prices: &[TokenPrice]) -> SynorDecimal {
        if prices.is_empty() {
            return Decimal::ZERO;
        }

        let sum: Decimal = prices.iter().map(|p| p.price).sum();
        sum / Decimal::from(prices.len())
    }

    /// Calculate volume-weighted average price (VWAP)
    /// Requires prices with volume data
    pub fn calculate_vwap(&self, prices: &[(TokenPrice, SynorDecimal)]) -> SynorDecimal {
        if prices.is_empty() {
            return Decimal::ZERO;
        }

        let mut weighted_sum = Decimal::ZERO;
        let mut total_volume = Decimal::ZERO;

        for (price, volume) in prices {
            weighted_sum += price.price * *volume;
            total_volume += *volume;
        }

        if total_volume == Decimal::ZERO {
            prices.last().map(|(p, _)| p.price).unwrap_or(Decimal::ZERO)
        } else {
            weighted_sum / total_volume
        }
    }

    /// Calculate price volatility (standard deviation)
    pub fn calculate_volatility(&self, prices: &[TokenPrice]) -> SynorDecimal {
        if prices.len() < 2 {
            return Decimal::ZERO;
        }

        let mean = self.calculate_sma(prices);
        let variance: Decimal = prices
            .iter()
            .map(|p| {
                let diff = p.price - mean;
                diff * diff
            })
            .sum::<Decimal>()
            / Decimal::from(prices.len());

        // Approximate square root using Newton's method
        let mut guess = variance;
        for _ in 0..10 {
            if guess == Decimal::ZERO {
                break;
            }
            guess = (guess + variance / guess) / Decimal::from(2);
        }

        guess
    }
}

/// TWAP observation for on-chain storage
#[derive(Debug, Clone)]
pub struct TwapObservation {
    pub timestamp: DateTime<Utc>,
    pub price_cumulative: SynorDecimal,
    pub seconds_per_liquidity_cumulative: SynorDecimal,
}

/// On-chain TWAP oracle (Uniswap V3 style)
pub struct OnChainTwap {
    observations: Vec<TwapObservation>,
    cardinality: usize,
    cardinality_next: usize,
}

impl OnChainTwap {
    pub fn new(cardinality: usize) -> Self {
        Self {
            observations: Vec::with_capacity(cardinality),
            cardinality,
            cardinality_next: cardinality,
        }
    }

    /// Record new price observation
    pub fn observe(&mut self, price: SynorDecimal, liquidity: SynorDecimal) {
        let last = self.observations.last();
        let timestamp = Utc::now();

        let (price_cumulative, spl_cumulative) = if let Some(last_obs) = last {
            let time_delta = (timestamp - last_obs.timestamp).num_seconds();
            let time_delta_dec = Decimal::from(time_delta);

            (
                last_obs.price_cumulative + price * time_delta_dec,
                last_obs.seconds_per_liquidity_cumulative
                    + if liquidity > Decimal::ZERO {
                        time_delta_dec / liquidity
                    } else {
                        Decimal::ZERO
                    },
            )
        } else {
            (Decimal::ZERO, Decimal::ZERO)
        };

        let observation = TwapObservation {
            timestamp,
            price_cumulative,
            seconds_per_liquidity_cumulative: spl_cumulative,
        };

        if self.observations.len() < self.cardinality {
            self.observations.push(observation);
        } else {
            // Ring buffer - overwrite oldest
            let index = self.observations.len() % self.cardinality;
            self.observations[index] = observation;
        }
    }

    /// Calculate TWAP between two timestamps
    pub fn consult(&self, seconds_ago: i64) -> Option<SynorDecimal> {
        if self.observations.len() < 2 {
            return None;
        }

        let now = Utc::now();
        let target_time = now - Duration::seconds(seconds_ago);

        // Find observations bracketing target_time
        let mut before: Option<&TwapObservation> = None;
        let mut after: Option<&TwapObservation> = None;

        for obs in &self.observations {
            if obs.timestamp <= target_time {
                before = Some(obs);
            } else if after.is_none() {
                after = Some(obs);
            }
        }

        match (before, after) {
            (Some(b), Some(a)) => {
                let time_delta = (a.timestamp - b.timestamp).num_seconds();
                if time_delta <= 0 {
                    return None;
                }
                let price_delta = a.price_cumulative - b.price_cumulative;
                Some(price_delta / Decimal::from(time_delta))
            }
            _ => None,
        }
    }

    /// Get the current number of observations
    pub fn observation_count(&self) -> usize {
        self.observations.len()
    }

    /// Get the cardinality (max observations)
    pub fn cardinality(&self) -> usize {
        self.cardinality
    }

    /// Get the next cardinality (pending growth)
    pub fn cardinality_next(&self) -> usize {
        self.cardinality_next
    }

    /// Increase the oracle cardinality (allows more observations to be stored)
    /// Similar to Uniswap V3's increaseObservationCardinalityNext
    pub fn increase_cardinality(&mut self, new_cardinality: usize) {
        if new_cardinality > self.cardinality_next {
            self.cardinality_next = new_cardinality;
        }
    }

    /// Apply the pending cardinality increase (called during next observation)
    pub fn apply_cardinality_growth(&mut self) {
        if self.cardinality_next > self.cardinality {
            self.observations.reserve(self.cardinality_next - self.cardinality);
            self.cardinality = self.cardinality_next;
        }
    }
}

/// Factory for creating on-chain TWAP oracles
pub struct OnChainTwapFactory;

impl OnChainTwapFactory {
    /// Create a standard TWAP oracle with default cardinality (24 observations)
    pub fn standard() -> OnChainTwap {
        OnChainTwap::new(24)
    }

    /// Create a high-frequency TWAP oracle (more observations)
    pub fn high_frequency() -> OnChainTwap {
        OnChainTwap::new(144) // 6 per hour * 24 hours
    }

    /// Create a low-frequency TWAP oracle (fewer observations)
    pub fn low_frequency() -> OnChainTwap {
        OnChainTwap::new(12)
    }

    /// Create a custom TWAP oracle with specified cardinality
    pub fn custom(cardinality: usize) -> OnChainTwap {
        OnChainTwap::new(cardinality)
    }
}

/// Builder for configuring and creating TWAP observation systems
pub struct TwapOracleBuilder {
    cardinality: usize,
    initial_observations: Vec<TwapObservation>,
}

impl TwapOracleBuilder {
    /// Create a new builder with default cardinality
    pub fn new() -> Self {
        Self {
            cardinality: 24,
            initial_observations: Vec::new(),
        }
    }

    /// Set the cardinality (max observations)
    pub fn with_cardinality(mut self, cardinality: usize) -> Self {
        self.cardinality = cardinality;
        self
    }

    /// Add an initial observation
    pub fn with_observation(mut self, timestamp: DateTime<Utc>, price_cumulative: SynorDecimal, spl_cumulative: SynorDecimal) -> Self {
        self.initial_observations.push(TwapObservation {
            timestamp,
            price_cumulative,
            seconds_per_liquidity_cumulative: spl_cumulative,
        });
        self
    }

    /// Build the OnChainTwap oracle
    pub fn build(self) -> OnChainTwap {
        let mut twap = OnChainTwap::new(self.cardinality);
        for obs in self.initial_observations {
            twap.observations.push(obs);
        }
        twap
    }
}

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

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

    fn make_price(price: Decimal, minutes_ago: i64) -> TokenPrice {
        TokenPrice {
            token: "SYNOR".to_string(),
            quote: "USD".to_string(),
            price,
            timestamp: Utc::now() - Duration::minutes(minutes_ago),
            source: PriceSource::Internal,
            confidence: 1.0,
        }
    }

    #[test]
    fn test_sma() {
        let calculator = TwapCalculator::new(Duration::hours(1));
        let prices = vec![
            make_price(dec!(100), 5),
            make_price(dec!(110), 4),
            make_price(dec!(105), 3),
            make_price(dec!(115), 2),
            make_price(dec!(120), 1),
        ];

        let sma = calculator.calculate_sma(&prices);
        assert_eq!(sma, dec!(110)); // (100+110+105+115+120)/5 = 110
    }

    #[test]
    fn test_twap_single_price() {
        let calculator = TwapCalculator::new(Duration::hours(1));
        let prices = vec![make_price(dec!(100), 5)];

        let twap = calculator.calculate(&prices);
        assert_eq!(twap, dec!(100));
    }

    #[test]
    fn test_volatility() {
        let calculator = TwapCalculator::new(Duration::hours(1));
        let prices = vec![
            make_price(dec!(100), 5),
            make_price(dec!(100), 4),
            make_price(dec!(100), 3),
        ];

        let volatility = calculator.calculate_volatility(&prices);
        assert_eq!(volatility, dec!(0)); // No variation
    }

    #[test]
    fn test_on_chain_twap() {
        let mut twap = OnChainTwap::new(10);

        // Add observations
        twap.observe(dec!(100), dec!(1000));
        std::thread::sleep(std::time::Duration::from_millis(10));
        twap.observe(dec!(110), dec!(1000));

        // Should have 2 observations
        assert_eq!(twap.observations.len(), 2);
    }
}
