use arrow::array::Float64Array;
use bt_analytics::extract_round_trip_pnls;
use bt_core::BacktestResult;
use pyo3::prelude::*;

use crate::arrow_interop::{array_to_pyarrow, batch_to_pyarrow};

#[pyclass(name = "BacktestResult")]
pub struct PyBacktestResult {
    inner: BacktestResult,
}

#[pymethods]
impl PyBacktestResult {
    #[getter]
    fn manifest(&self, py: Python<'_>) -> PyResult<PyObject> {
        let json = serde_json::to_string(&self.inner.manifest).map_err(crate::map_json_error)?;
        let json_mod = py.import("json")?;
        json_mod
            .call_method1("loads", (json,))
            .map(|obj| obj.into())
    }

    #[getter]
    fn metrics(&self, py: Python<'_>) -> PyResult<PyObject> {
        let json = serde_json::to_string(&self.inner.metrics).map_err(crate::map_json_error)?;
        let json_mod = py.import("json")?;
        json_mod
            .call_method1("loads", (json,))
            .map(|obj| obj.into())
    }

    #[getter]
    fn equity_curve(&self, py: Python<'_>) -> PyResult<PyObject> {
        array_to_pyarrow(py, &self.inner.equity_curve)
    }

    #[getter]
    fn positions(&self, py: Python<'_>) -> PyResult<PyObject> {
        batch_to_pyarrow(py, &self.inner.positions)
    }

    #[getter]
    fn trades(&self, py: Python<'_>) -> PyResult<PyObject> {
        batch_to_pyarrow(py, &self.inner.trades)
    }

    #[getter]
    fn daily_returns(&self, py: Python<'_>) -> PyResult<PyObject> {
        array_to_pyarrow(py, &self.inner.daily_returns)
    }

    #[getter]
    fn warnings(&self) -> Vec<String> {
        self.inner.warnings.clone()
    }

    #[getter]
    fn trade_count(&self) -> usize {
        self.inner.trades.num_rows()
    }

    #[getter]
    fn profile(&self, py: Python<'_>) -> PyResult<PyObject> {
        let json = serde_json::to_string(&self.inner.profile).map_err(crate::map_json_error)?;
        let json_mod = py.import("json")?;
        json_mod
            .call_method1("loads", (json,))
            .map(|obj| obj.into())
    }
}

impl PyBacktestResult {
    pub fn new(inner: BacktestResult) -> Self {
        Self { inner }
    }

    /// Bar-level equity values as a plain Vec for Monte Carlo input.
    pub(crate) fn equity_as_f64(&self) -> Vec<f64> {
        self.inner
            .equity_curve
            .as_any()
            .downcast_ref::<Float64Array>()
            .map(|arr| arr.values().to_vec())
            .unwrap_or_default()
    }

    /// Round-trip net PnL (after fees) for each completed trade.
    /// Returns an empty Vec when there are no trades or the batch schema is unexpected.
    pub(crate) fn trade_pnls(&self) -> Vec<f64> {
        extract_round_trip_pnls(&self.inner.trades)
    }
}
