use serde::{Deserialize, Serialize};

#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum Side {
    Buy,
    Sell,
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct BarData {
    pub volume: f64,
    pub spread: Option<f64>,
    pub is_gap: bool,
    pub high: f64,
    pub low: f64,
}

pub trait SlippageModel: Send + Sync {
    fn adjusted_price(&self, intended_price: f64, quantity: f64, side: Side, bar: &BarData) -> f64;
}

#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct FixedBpsSlippage {
    pub bps: f64,
}

impl SlippageModel for FixedBpsSlippage {
    fn adjusted_price(
        &self,
        intended_price: f64,
        _quantity: f64,
        side: Side,
        _bar: &BarData,
    ) -> f64 {
        let direction = match side {
            Side::Buy => 1.0,
            Side::Sell => -1.0,
        };
        intended_price * (1.0 + direction * self.bps / 10_000.0)
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct VolumeImpactSlippage {
    pub impact_coeff: f64,
    pub exponent: f64,
}

impl SlippageModel for VolumeImpactSlippage {
    fn adjusted_price(&self, intended_price: f64, quantity: f64, side: Side, bar: &BarData) -> f64 {
        let participation = quantity.abs() / bar.volume.max(1.0);
        let impact = self.impact_coeff * participation.powf(self.exponent);
        let direction = match side {
            Side::Buy => 1.0,
            Side::Sell => -1.0,
        };
        intended_price * (1.0 + direction * impact)
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct SpreadBasedSlippage {
    pub spread_fraction: f64,
}

impl SlippageModel for SpreadBasedSlippage {
    fn adjusted_price(
        &self,
        intended_price: f64,
        _quantity: f64,
        side: Side,
        bar: &BarData,
    ) -> f64 {
        let half_spread = bar.spread.unwrap_or(0.0) * self.spread_fraction / 2.0;
        let direction = match side {
            Side::Buy => 1.0,
            Side::Sell => -1.0,
        };
        intended_price + direction * half_spread
    }
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum SlippageConfig {
    FixedBps { bps: f64 },
    VolumeImpact { impact_coeff: f64, exponent: f64 },
    SpreadBased { spread_fraction: f64 },
}

impl Default for SlippageConfig {
    fn default() -> Self {
        Self::FixedBps { bps: 0.0 }
    }
}

impl SlippageConfig {
    pub fn adjusted_price(
        &self,
        intended_price: f64,
        quantity: f64,
        side: Side,
        bar: &BarData,
        warnings: &mut Vec<String>,
    ) -> f64 {
        match self {
            Self::FixedBps { bps } => {
                FixedBpsSlippage { bps: *bps }.adjusted_price(intended_price, quantity, side, bar)
            }
            Self::VolumeImpact {
                impact_coeff,
                exponent,
            } => VolumeImpactSlippage {
                impact_coeff: *impact_coeff,
                exponent: *exponent,
            }
            .adjusted_price(intended_price, quantity, side, bar),
            Self::SpreadBased { spread_fraction } => {
                if bar.spread.is_none() {
                    warnings.push(
                        "spread column missing; fallback to FixedBpsSlippage(5.0)".to_string(),
                    );
                    return FixedBpsSlippage { bps: 5.0 }.adjusted_price(
                        intended_price,
                        quantity,
                        side,
                        bar,
                    );
                }

                SpreadBasedSlippage {
                    spread_fraction: *spread_fraction,
                }
                .adjusted_price(intended_price, quantity, side, bar)
            }
        }
    }
}
