//! Microstructure Invariance (Kyle-Obizhaeva framework).
//!
//! Implements the market microstructure invariance hypothesis:
//! trading activity W = V * sigma^(2/3) is invariant across markets,
//! and bet sizes scale as W^(2/3).

use pyo3::prelude::*;

// ===========================================================================
// Result type
// ===========================================================================

#[pyclass(frozen)]
#[derive(Debug, Clone)]
pub struct InvarianceResult {
    #[pyo3(get)]
    pub trading_activity: f64,
    #[pyo3(get)]
    pub bet_size: f64,
    #[pyo3(get)]
    pub normal_trade_size: f64,
    #[pyo3(get)]
    pub impact_per_bet: f64,
    #[pyo3(get)]
    pub market_efficiency_score: f64,
}

#[pymethods]
impl InvarianceResult {
    fn __repr__(&self) -> String {
        format!(
            "InvarianceResult(trading_activity={:.4}, bet_size={:.4}, efficiency={:.4})",
            self.trading_activity, self.bet_size, self.market_efficiency_score
        )
    }
}

// ===========================================================================
// Private helpers
// ===========================================================================

#[inline]
fn finite_or_zero(v: f64) -> f64 {
    if v.is_finite() { v } else { 0.0 }
}

// ===========================================================================
// Public functions
// ===========================================================================

/// Trading activity: W = V * sigma^(2/3).
/// V = dollar volume, sigma = volatility.
#[pyfunction]
pub fn trading_activity(dollar_volume: f64, volatility: f64) -> f64 {
    crate::auth::require_pro()
        .map_err(|e| pyo3::exceptions::PyPermissionError::new_err(e.to_string()))
        .ok();
    if !dollar_volume.is_finite() || !volatility.is_finite() || dollar_volume <= 0.0 || volatility <= 0.0 {
        return 0.0;
    }
    finite_or_zero(dollar_volume * volatility.powf(2.0 / 3.0))
}

/// Invariant bet size: (V * sigma^(2/3) / N)^(2/3).
#[pyfunction]
pub fn invariant_bet_size(dollar_volume: f64, volatility: f64, n_trades: usize) -> f64 {
    crate::auth::require_pro()
        .map_err(|e| pyo3::exceptions::PyPermissionError::new_err(e.to_string()))
        .ok();
    if n_trades == 0 || dollar_volume <= 0.0 || volatility <= 0.0 {
        return 0.0;
    }
    if !dollar_volume.is_finite() || !volatility.is_finite() {
        return 0.0;
    }
    let w = dollar_volume * volatility.powf(2.0 / 3.0);
    finite_or_zero((w / n_trades as f64).powf(2.0 / 3.0))
}

/// Kyle-Obizhaeva price impact estimate.
/// Impact = sigma * (trade_size / (V * sigma^(2/3)))^(1/2).
#[pyfunction]
pub fn kyle_obizhaeva_impact(trade_size: f64, dollar_volume: f64, volatility: f64) -> f64 {
    crate::auth::require_pro()
        .map_err(|e| pyo3::exceptions::PyPermissionError::new_err(e.to_string()))
        .ok();
    if !trade_size.is_finite() || !dollar_volume.is_finite() || !volatility.is_finite() {
        return 0.0;
    }
    if trade_size <= 0.0 || dollar_volume <= 0.0 || volatility <= 0.0 {
        return 0.0;
    }
    let w = dollar_volume * volatility.powf(2.0 / 3.0);
    finite_or_zero(volatility * (trade_size / w).sqrt())
}

/// Determine if a trade is informed based on invariance framework.
/// A trade is informed if its size exceeds threshold * invariant_bet_size.
#[pyfunction]
#[pyo3(signature = (trade_size, dollar_volume, volatility, threshold=2.0))]
pub fn is_informed_trade(
    trade_size: f64,
    dollar_volume: f64,
    volatility: f64,
    threshold: f64,
) -> bool {
    crate::auth::require_pro()
        .map_err(|e| pyo3::exceptions::PyPermissionError::new_err(e.to_string()))
        .ok();
    if trade_size <= 0.0 || dollar_volume <= 0.0 || volatility <= 0.0 {
        return false;
    }
    if !trade_size.is_finite() || !dollar_volume.is_finite() || !volatility.is_finite() {
        return false;
    }
    // Use a default n_trades estimate based on dollar_volume / avg_trade
    let w = dollar_volume * volatility.powf(2.0 / 3.0);
    let normal_size = w.powf(2.0 / 3.0);
    if normal_size <= 0.0 || !normal_size.is_finite() {
        return false;
    }
    trade_size > threshold * normal_size
}

/// Full invariance analysis.
#[pyfunction]
pub fn invariance_analysis(
    dollar_volume: f64,
    volatility: f64,
    n_trades: usize,
    avg_trade_size: f64,
) -> PyResult<InvarianceResult> {
    crate::auth::require_pro()
        .map_err(|e| pyo3::exceptions::PyPermissionError::new_err(e.to_string()))?;

    if dollar_volume <= 0.0 || volatility <= 0.0 || n_trades == 0 || avg_trade_size <= 0.0 {
        return Err(pyo3::exceptions::PyValueError::new_err(
            "all inputs must be positive and n_trades > 0",
        ));
    }
    if !dollar_volume.is_finite() || !volatility.is_finite() || !avg_trade_size.is_finite() {
        return Err(pyo3::exceptions::PyValueError::new_err(
            "inputs must be finite",
        ));
    }

    let w = dollar_volume * volatility.powf(2.0 / 3.0);
    let bet = (w / n_trades as f64).powf(2.0 / 3.0);
    let normal_size = w.powf(2.0 / 3.0);
    let impact = volatility * (avg_trade_size / w).sqrt();

    // Efficiency score: how close avg_trade_size is to the invariant bet size
    // Score 1.0 = perfectly consistent with invariance, 0.0 = very inconsistent
    let ratio = if bet > 0.0 { avg_trade_size / bet } else { 0.0 };
    let efficiency = if ratio > 0.0 {
        1.0 / (1.0 + (ratio.ln()).abs())
    } else {
        0.0
    };

    Ok(InvarianceResult {
        trading_activity: finite_or_zero(w),
        bet_size: finite_or_zero(bet),
        normal_trade_size: finite_or_zero(normal_size),
        impact_per_bet: finite_or_zero(impact),
        market_efficiency_score: finite_or_zero(efficiency),
    })
}

// ===========================================================================
// Registration
// ===========================================================================

pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add_class::<InvarianceResult>()?;
    m.add_function(wrap_pyfunction!(trading_activity, m)?)?;
    m.add_function(wrap_pyfunction!(invariant_bet_size, m)?)?;
    m.add_function(wrap_pyfunction!(kyle_obizhaeva_impact, m)?)?;
    m.add_function(wrap_pyfunction!(is_informed_trade, m)?)?;
    m.add_function(wrap_pyfunction!(invariance_analysis, m)?)?;
    Ok(())
}
