//! PyO3 bindings for portfolio backtesting.

use bt_core::portfolio_model::{PortfolioDef, RebalanceConfig, RiskRule, StrategySlot};
use bt_core::portfolio_runner;
use bt_strategy::compile_strategy;
use pyo3::prelude::*;
use serde::Deserialize;

use crate::data::PyDataStore;
use crate::result::PyBacktestResult;

/// JSON-serializable portfolio definition from Python.
#[derive(Debug, Deserialize)]
struct PortfolioJson {
    strategies: Vec<StrategySlotJson>,
    #[serde(default)]
    risk_rules: Vec<RiskRuleJson>,
    #[serde(default)]
    rebalance: RebalanceJson,
}

#[derive(Debug, Deserialize)]
struct StrategySlotJson {
    name: String,
    strategy_json: String,
    weight: f64,
}

#[derive(Debug, Deserialize)]
#[serde(tag = "type")]
enum RiskRuleJson {
    MaxDrawdown { threshold_pct: f64 },
    StrategyKillSwitch { strategy: String, max_loss_pct: f64 },
    MaxGrossExposure { max_pct: f64 },
    MaxNetExposure { max_pct: f64 },
}

#[derive(Debug, Default, Deserialize)]
#[serde(tag = "type")]
enum RebalanceJson {
    #[default]
    None,
    Periodic { every_n_bars: u32 },
    Threshold { drift_pct: f64 },
}

/// Run a portfolio backtest with multiple strategies.
#[pyfunction]
pub fn run_portfolio(
    portfolio_json: &str,
    config_json: &str,
    store: &PyDataStore,
) -> PyResult<(PyBacktestResult, PyObject)> {
    let pf: PortfolioJson = serde_json::from_str(portfolio_json).map_err(crate::map_json_error)?;
    let config: bt_core::BacktestConfig =
        serde_json::from_str(config_json).map_err(crate::map_json_error)?;

    // Compile each strategy.
    let mut slots = Vec::new();
    for slot_json in &pf.strategies {
        let strategy_def: bt_strategy::StrategyDef =
            serde_json::from_str(&slot_json.strategy_json).map_err(crate::map_json_error)?;
        let plan = compile_strategy(&strategy_def).map_err(crate::map_bt_error)?;
        slots.push(StrategySlot {
            name: slot_json.name.clone(),
            plan,
            weight: slot_json.weight,
        });
    }

    // Convert risk rules.
    let risk_rules: Vec<RiskRule> = pf
        .risk_rules
        .into_iter()
        .map(|r| match r {
            RiskRuleJson::MaxDrawdown { threshold_pct } => RiskRule::MaxDrawdown { threshold_pct },
            RiskRuleJson::StrategyKillSwitch {
                strategy,
                max_loss_pct,
            } => RiskRule::StrategyKillSwitch {
                strategy,
                max_loss_pct,
            },
            RiskRuleJson::MaxGrossExposure { max_pct } => RiskRule::MaxGrossExposure { max_pct },
            RiskRuleJson::MaxNetExposure { max_pct } => RiskRule::MaxNetExposure { max_pct },
        })
        .collect();

    let rebalance = match pf.rebalance {
        RebalanceJson::None => RebalanceConfig::None,
        RebalanceJson::Periodic { every_n_bars } => RebalanceConfig::Periodic { every_n_bars },
        RebalanceJson::Threshold { drift_pct } => RebalanceConfig::Threshold { drift_pct },
    };

    let portfolio_def = PortfolioDef {
        strategies: slots,
        risk_rules,
        rebalance,
    };

    // Run.
    let result = portfolio_runner::run_portfolio(&portfolio_def, &config, store.store())
        .map_err(crate::map_bt_error)?;

    // Convert combined result.
    let combined = PyBacktestResult::new(result.combined);

    // Build per-strategy info as a Python dict.
    Python::with_gil(|py| {
        let dict = pyo3::types::PyDict::new(py);
        for (name, per) in &result.per_strategy {
            let info = pyo3::types::PyDict::new(py);
            info.set_item("name", &per.name)?;
            info.set_item("trade_count", per.trade_count)?;
            info.set_item("final_capital", per.final_capital)?;
            info.set_item("killed", per.killed)?;
            info.set_item(
                "equity_curve",
                pyo3::types::PyList::new(py, &per.equity_curve)?,
            )?;
            dict.set_item(name, info)?;
        }

        // Add risk events.
        let risk_list = pyo3::types::PyList::empty(py);
        for event in &result.risk_events {
            let d = pyo3::types::PyDict::new(py);
            d.set_item("rule", &event.rule)?;
            d.set_item("action", &event.action)?;
            risk_list.append(d)?;
        }
        dict.set_item("_risk_events", risk_list)?;

        Ok((combined, dict.into()))
    })
}
