use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use serde_json::Value;

use super::strategy_config::StrategyConfig;

/// Top-level run configuration for a single-instrument paper trading run.
///
/// Uses swym-specific types (strings, singular fields) rather than barter internals.
/// The paper-executor maps this into barter's `SystemConfig` before engine construction.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RunConfig {
    /// Instrument configuration — passed through as JSON to barter's `InstrumentConfig`.
    pub instrument: Value,
    /// Mock execution parameters (exchange, latency, fees, initial account state).
    pub execution: MockExecutionConfig,
    /// Strategy selection and parameters.
    #[serde(default)]
    pub strategy: StrategyConfig,
    /// When `true`, record per-bar per-condition evaluation results during a backtest.
    /// Stored in `paper_run_condition_audit` and retrievable via the API for download.
    /// Also enables the always-on condition pass-rate summary in `result_summary`.
    /// Has no effect on live runs. Defaults to `true`.
    #[serde(default = "default_true")]
    pub audit_conditions: bool,
}

fn default_true() -> bool { true }

/// Mock execution configuration using plain strings instead of barter enums.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct MockExecutionConfig {
    pub mocked_exchange: String,
    pub latency_ms: u64,
    pub fees_percent: Decimal,
    pub initial_state: AccountState,
}

/// Initial account state for a single exchange + single instrument.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AccountState {
    pub exchange: String,
    pub balances: Vec<AssetBalance>,
    /// Singular instrument (not a vec) — a paper run targets one instrument.
    pub instrument: InstrumentOrders,
}

/// Balance snapshot for a single asset.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AssetBalance {
    pub asset: String,
    pub balance: Balance,
    pub time_exchange: DateTime<Utc>,
}

/// Total and free balance for an asset.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Balance {
    pub total: Decimal,
    pub free: Decimal,
}

/// Open orders for a single instrument, keyed by name.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct InstrumentOrders {
    pub instrument_name: String,
    #[serde(default)]
    pub orders: Vec<Value>,
}

#[cfg(test)]
mod tests {
    use super::*;

    fn sample_run_config_json() -> &'static str {
        r#"{
            "instrument": {
                "exchange": "binance_spot",
                "name_exchange": "BTCUSDT",
                "underlying": { "base": "btc", "quote": "usdt" },
                "quote": "underlying_quote",
                "kind": "spot"
            },
            "execution": {
                "mocked_exchange": "binance_spot",
                "latency_ms": 100,
                "fees_percent": 0.05,
                "initial_state": {
                    "exchange": "binance_spot",
                    "balances": [
                        {
                            "asset": "usdt",
                            "balance": { "total": 10000, "free": 10000 },
                            "time_exchange": "2025-03-24T21:30:00Z"
                        }
                    ],
                    "instrument": {
                        "instrument_name": "BTCUSDT",
                        "orders": []
                    }
                }
            },
            "strategy": {
                "type": "default"
            }
        }"#
    }

    #[test]
    fn round_trip_serde() {
        let config: RunConfig = serde_json::from_str(sample_run_config_json()).unwrap();

        assert_eq!(config.execution.mocked_exchange, "binance_spot");
        assert_eq!(config.execution.latency_ms, 100);
        assert_eq!(
            config.execution.initial_state.instrument.instrument_name,
            "BTCUSDT"
        );
        assert!(config.execution.initial_state.instrument.orders.is_empty());
        assert_eq!(config.execution.initial_state.balances.len(), 1);

        // Re-serialize and deserialize again
        let json = serde_json::to_string(&config).unwrap();
        let _roundtrip: RunConfig = serde_json::from_str(&json).unwrap();
    }

    #[test]
    fn old_barter_shape_fails_to_parse() {
        // Uses plural "instruments" inside initial_state — should fail
        let old_shape = r#"{
            "instrument": { "exchange": "binance_spot", "name_exchange": "BTCUSDT", "underlying": { "base": "btc", "quote": "usdt" }, "quote": "underlying_quote", "kind": "spot" },
            "execution": {
                "mocked_exchange": "binance_spot",
                "latency_ms": 100,
                "fees_percent": 0.05,
                "initial_state": {
                    "exchange": "binance_spot",
                    "balances": [],
                    "instruments": [{ "instrument": "BTCUSDT", "orders": [] }]
                }
            }
        }"#;
        let result = serde_json::from_str::<RunConfig>(old_shape);
        assert!(result.is_err(), "old plural shape should not parse");
    }

    #[test]
    fn orders_default_to_empty() {
        let json = r#"{
            "instrument": {},
            "execution": {
                "mocked_exchange": "binance_spot",
                "latency_ms": 100,
                "fees_percent": 0.05,
                "initial_state": {
                    "exchange": "binance_spot",
                    "balances": [],
                    "instrument": { "instrument_name": "BTCUSDT" }
                }
            }
        }"#;
        let config: RunConfig = serde_json::from_str(json).unwrap();
        assert!(config.execution.initial_state.instrument.orders.is_empty());
    }

    #[test]
    fn strategy_defaults_to_default() {
        // No strategy field — should default to StrategyConfig::Default
        let json = r#"{
            "instrument": {},
            "execution": {
                "mocked_exchange": "binance_spot",
                "latency_ms": 100,
                "fees_percent": 0.05,
                "initial_state": {
                    "exchange": "binance_spot",
                    "balances": [],
                    "instrument": { "instrument_name": "BTCUSDT" }
                }
            }
        }"#;
        let config: RunConfig = serde_json::from_str(json).unwrap();
        assert!(matches!(config.strategy, StrategyConfig::Default));
    }
}
