use std::error::Error;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};

use chrono::{NaiveDate, NaiveTime};
use fidc_core::{
    BacktestConfig, BacktestEngine, BenchmarkSnapshot, BrokerSimulator, ChinaAShareCostModel,
    ChinaEquityRuleHooks, CnSmallCapRotationConfig, CnSmallCapRotationStrategy, DailyEquityPoint,
    DataSet, FillEvent, HoldingSummary, JqMicroCapConfig, JqMicroCapStrategy, PortfolioState,
    PriceField, Strategy, StrategyContext,
};
use serde_json::json;

fn main() -> Result<(), Box<dyn Error>> {
    let root = workspace_root();
    let data_dir = std::env::var("FIDC_BT_DATA_DIR")
        .map(PathBuf::from)
        .unwrap_or_else(|_| root.join("data/demo"));
    let data_layout = std::env::var("FIDC_BT_DATA_LAYOUT").unwrap_or_else(|_| "flat".to_string());
    let output_dir = std::env::var("FIDC_BT_OUTPUT_DIR")
        .map(PathBuf::from)
        .unwrap_or_else(|_| root.join("output/demo"));
    let json_output = std::env::var("FIDC_BT_JSON")
        .map(|value| value == "1" || value.eq_ignore_ascii_case("true"))
        .unwrap_or(false);

    fs::create_dir_all(&output_dir)?;

    let data = if data_layout == "partitioned" {
        DataSet::from_partitioned_dir(&data_dir)?
    } else {
        DataSet::from_csv_dir(&data_dir)?
    };
    let strategy_name =
        std::env::var("FIDC_BT_STRATEGY").unwrap_or_else(|_| "cn-smallcap-rotation".to_string());
    let debug_date = std::env::var("FIDC_BT_DEBUG_DATE")
        .ok()
        .filter(|value| !value.trim().is_empty())
        .map(|value| NaiveDate::parse_from_str(value.trim(), "%Y-%m-%d"))
        .transpose()?;
    let decision_lag = std::env::var("FIDC_BT_DECISION_LAG")
        .ok()
        .and_then(|value| value.parse::<usize>().ok());
    let execution_price =
        std::env::var("FIDC_BT_EXECUTION_PRICE")
            .ok()
            .map(|value| match value.as_str() {
                "close" => PriceField::Close,
                "last" => PriceField::Last,
                _ => PriceField::Open,
            });
    let initial_cash = std::env::var("FIDC_BT_INITIAL_CASH")
        .ok()
        .and_then(|value| value.parse::<f64>().ok());
    let start_date = std::env::var("FIDC_BT_START_DATE")
        .ok()
        .filter(|value| !value.trim().is_empty())
        .map(|value| NaiveDate::parse_from_str(value.trim(), "%Y-%m-%d"))
        .transpose()?;
    let end_date = std::env::var("FIDC_BT_END_DATE")
        .ok()
        .filter(|value| !value.trim().is_empty())
        .map(|value| NaiveDate::parse_from_str(value.trim(), "%Y-%m-%d"))
        .transpose()?;
    let mut config = BacktestConfig {
        initial_cash: initial_cash.unwrap_or(1_000_000.0),
        benchmark_code: data.benchmark_code().to_string(),
        start_date,
        end_date,
        decision_lag_trading_days: 1,
        execution_price_field: PriceField::Open,
    };
    let result = match strategy_name.as_str() {
        "cn-smallcap-rotation" | "cn-dyn-smallcap-band" => {
            let mut strategy_cfg = if strategy_name == "cn-dyn-smallcap-band" {
                CnSmallCapRotationConfig::cn_dyn_smallcap_band()
            } else {
                CnSmallCapRotationConfig::demo()
            };
            if strategy_cfg.strategy_name == "cn-smallcap-rotation" {
                strategy_cfg.base_index_level = 3000.0;
                strategy_cfg.base_cap_floor = 38.0;
                strategy_cfg.cap_span = 25.0;
            }
            if let Ok(signal_symbol) = std::env::var("FIDC_BT_SIGNAL_SYMBOL") {
                if !signal_symbol.trim().is_empty() {
                    strategy_cfg.signal_symbol = Some(signal_symbol);
                }
            }
            config.decision_lag_trading_days = decision_lag.unwrap_or(1);
            config.execution_price_field = execution_price.unwrap_or(PriceField::Open);
            let strategy = CnSmallCapRotationStrategy::new(strategy_cfg);
            let broker = BrokerSimulator::new_with_execution_price(
                ChinaAShareCostModel::default(),
                ChinaEquityRuleHooks::default(),
                config.execution_price_field,
            );
            let mut engine = BacktestEngine::new(data, strategy, broker, config);
            engine.run()?
        }
        _ => {
            let mut strategy_cfg = JqMicroCapConfig::jq_microcap();
            if let Ok(signal_symbol) = std::env::var("FIDC_BT_SIGNAL_SYMBOL") {
                if !signal_symbol.trim().is_empty() {
                    strategy_cfg.benchmark_signal_symbol = signal_symbol;
                }
            }
            if let Some(date) = debug_date {
                let eligible = data.eligible_universe_on(date);
                eprintln!(
                    "DEBUG eligible_universe_on {} count={}",
                    date,
                    eligible.len()
                );
                for row in eligible.iter().take(20) {
                    eprintln!("  {} {:.6}", row.symbol, row.market_cap_bn);
                }
                let mut debug_strategy = JqMicroCapStrategy::new(strategy_cfg.clone());
                let decision = debug_strategy.on_day(&StrategyContext {
                    execution_date: date,
                    decision_date: date,
                    decision_index: 1,
                    data: &data,
                    portfolio: &PortfolioState::new(10_000_000.0),
                })?;
                eprintln!("DEBUG notes={:?}", decision.notes);
                eprintln!("DEBUG diagnostics={:?}", decision.diagnostics);
                return Ok(());
            }
            config.decision_lag_trading_days = decision_lag.unwrap_or(0);
            config.execution_price_field = execution_price.unwrap_or(PriceField::Last);
            config.initial_cash = initial_cash.unwrap_or(10_000_000.0);
            let strategy = JqMicroCapStrategy::new(strategy_cfg);
            let broker = BrokerSimulator::new_with_execution_price(
                ChinaAShareCostModel::default(),
                ChinaEquityRuleHooks::default(),
                config.execution_price_field,
            )
            .with_intraday_execution_start_time(
                NaiveTime::parse_from_str("10:18:00", "%H:%M:%S").expect("valid 10:18:00"),
            )
            .with_volume_limit(false)
            .with_inactive_limit(false)
            .with_liquidity_limit(false);
            let mut engine = BacktestEngine::new(data, strategy, broker, config);
            engine.run()?
        }
    };

    write_equity_curve_csv(&output_dir.join("equity_curve.csv"), &result.equity_curve)?;
    write_trades_csv(&output_dir.join("trades.csv"), &result.fills)?;
    write_holdings_csv(
        &output_dir.join("holdings_summary.csv"),
        &result.holdings_summary,
    )?;

    let summary = build_summary(
        &result.strategy_name,
        &result.equity_curve,
        &result.fills,
        &result.holdings_summary,
        result.benchmark_series.last(),
        &output_dir,
    );

    print_summary(&summary, &result.equity_curve, &result.holdings_summary);
    println!("Artifacts written under {}", output_dir.display());
    if json_output {
        println!("{}", serde_json::to_string(&summary)?);
    }
    Ok(())
}

fn workspace_root() -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("../..")
        .canonicalize()
        .expect("workspace root")
}

fn write_equity_curve_csv(path: &Path, rows: &[DailyEquityPoint]) -> Result<(), Box<dyn Error>> {
    let mut file = fs::File::create(path)?;
    writeln!(
        file,
        "date,cash,market_value,total_equity,benchmark_close,notes,diagnostics"
    )?;
    for row in rows {
        writeln!(
            file,
            "{},{:.2},{:.2},{:.2},{:.2},{},{}",
            row.date,
            row.cash,
            row.market_value,
            row.total_equity,
            row.benchmark_close,
            sanitize_csv_field(&row.notes),
            sanitize_csv_field(&row.diagnostics),
        )?;
    }
    Ok(())
}

fn write_trades_csv(path: &Path, rows: &[FillEvent]) -> Result<(), Box<dyn Error>> {
    let mut file = fs::File::create(path)?;
    writeln!(
        file,
        "date,symbol,side,quantity,price,gross_amount,commission,stamp_tax,net_cash_flow,reason"
    )?;
    for row in rows {
        writeln!(
            file,
            "{},{},{:?},{},{:.2},{:.2},{:.2},{:.2},{:.2},{}",
            row.date,
            row.symbol,
            row.side,
            row.quantity,
            row.price,
            row.gross_amount,
            row.commission,
            row.stamp_tax,
            row.net_cash_flow,
            sanitize_csv_field(&row.reason),
        )?;
    }
    Ok(())
}

fn write_holdings_csv(path: &Path, rows: &[HoldingSummary]) -> Result<(), Box<dyn Error>> {
    let mut file = fs::File::create(path)?;
    writeln!(
        file,
        "date,symbol,quantity,average_cost,last_price,market_value,unrealized_pnl,realized_pnl"
    )?;
    for row in rows {
        writeln!(
            file,
            "{},{},{},{:.2},{:.2},{:.2},{:.2},{:.2}",
            row.date,
            row.symbol,
            row.quantity,
            row.average_cost,
            row.last_price,
            row.market_value,
            row.unrealized_pnl,
            row.realized_pnl,
        )?;
    }
    Ok(())
}

fn sanitize_csv_field(text: &str) -> String {
    text.replace(',', ";")
}

#[derive(Debug, serde::Serialize)]
struct RunSummary {
    strategy: String,
    start_date: String,
    end_date: String,
    start_equity: f64,
    final_equity: f64,
    total_return: f64,
    trade_count: usize,
    holding_count: usize,
    benchmark_code: Option<String>,
    benchmark_last_close: Option<f64>,
    output_dir: String,
    diagnostics: serde_json::Value,
    warnings: Vec<String>,
    equity_preview: Vec<serde_json::Value>,
    trades_preview: Vec<serde_json::Value>,
}

fn build_summary(
    strategy_name: &str,
    equity_curve: &[DailyEquityPoint],
    fills: &[FillEvent],
    holdings: &[HoldingSummary],
    benchmark_last: Option<&BenchmarkSnapshot>,
    output_dir: &Path,
) -> RunSummary {
    let first = equity_curve.first();
    let last = equity_curve.last();
    let start_equity = first.map(|row| row.total_equity).unwrap_or_default();
    let final_equity = last.map(|row| row.total_equity).unwrap_or_default();
    let total_return = if start_equity.abs() < f64::EPSILON {
        0.0
    } else {
        (final_equity / start_equity) - 1.0
    };

    let diagnostics = extract_diagnostics(equity_curve);
    let warnings = build_warnings(fills, holdings, &diagnostics);
    let equity_preview = equity_curve
        .iter()
        .rev()
        .take(5)
        .collect::<Vec<_>>()
        .into_iter()
        .rev()
        .map(|row| {
            json!({
                "date": row.date.to_string(),
                "cash": row.cash,
                "marketValue": row.market_value,
                "totalEquity": row.total_equity,
                "benchmarkClose": row.benchmark_close,
                "notes": row.notes,
                "diagnostics": row.diagnostics,
            })
        })
        .collect::<Vec<_>>();
    let trades_preview = fills
        .iter()
        .rev()
        .take(10)
        .collect::<Vec<_>>()
        .into_iter()
        .rev()
        .map(|row| {
            json!({
                "date": row.date.to_string(),
                "symbol": row.symbol,
                "side": format!("{:?}", row.side),
                "quantity": row.quantity,
                "price": row.price,
                "grossAmount": row.gross_amount,
                "netCashFlow": row.net_cash_flow,
                "reason": row.reason,
            })
        })
        .collect::<Vec<_>>();

    RunSummary {
        strategy: strategy_name.to_string(),
        start_date: first.map(|row| row.date.to_string()).unwrap_or_default(),
        end_date: last.map(|row| row.date.to_string()).unwrap_or_default(),
        start_equity,
        final_equity,
        total_return,
        trade_count: fills.len(),
        holding_count: holdings.len(),
        benchmark_code: benchmark_last.map(|row| row.benchmark.clone()),
        benchmark_last_close: benchmark_last.map(|row| row.close),
        output_dir: output_dir.display().to_string(),
        diagnostics,
        warnings,
        equity_preview,
        trades_preview,
    }
}

fn extract_diagnostics(equity_curve: &[DailyEquityPoint]) -> serde_json::Value {
    let last = equity_curve.last();
    let text = last.map(|row| row.diagnostics.as_str()).unwrap_or("");
    let notes = last.map(|row| row.notes.as_str()).unwrap_or("");
    let mut map = serde_json::Map::new();
    map.insert("latestText".to_string(), json!(text));
    map.insert("latestNotes".to_string(), json!(notes));
    map.insert("equityPointCount".to_string(), json!(equity_curve.len()));

    for part in text.split(" | ") {
        let part = part.trim();
        if let Some(rest) = part.strip_prefix("selection_diag ") {
            for token in rest.split_whitespace() {
                if let Some((k, v)) = token.split_once('=') {
                    map.insert(k.to_string(), parse_diag_value(v));
                }
            }
        } else if let Some(rest) = part.strip_prefix("selection_band ") {
            for token in rest.split_whitespace() {
                if let Some((k, v)) = token.split_once('=') {
                    map.insert(k.to_string(), parse_diag_value(v));
                }
            }
        } else if let Some(rest) =
            part.strip_prefix("market_cap_missing likely blocks selection; sample=")
        {
            map.insert(
                "marketCapMissingSample".to_string(),
                json!(
                    rest.split('|')
                        .filter(|s| !s.is_empty())
                        .collect::<Vec<_>>()
                ),
            );
        } else if let Some(rest) = part.strip_prefix("selection_rejections sample=") {
            map.insert(
                "selectionRejectionsSample".to_string(),
                json!(
                    rest.split(" | ")
                        .filter(|s| !s.is_empty())
                        .collect::<Vec<_>>()
                ),
            );
        } else if let Some(rest) = part.strip_prefix("ma_filter_rejections sample=") {
            map.insert(
                "maFilterRejectionsSample".to_string(),
                json!(
                    rest.split('|')
                        .filter(|s| !s.is_empty())
                        .collect::<Vec<_>>()
                ),
            );
        } else if let Some(rest) = part.strip_prefix("selected=") {
            map.insert("selectedLine".to_string(), json!(rest));
        }
    }

    serde_json::Value::Object(map)
}

fn parse_diag_value(value: &str) -> serde_json::Value {
    if let Ok(v) = value.parse::<i64>() {
        return json!(v);
    }
    if let Ok(v) = value.parse::<f64>() {
        return json!(v);
    }
    json!(value)
}

fn build_warnings(
    fills: &[FillEvent],
    holdings: &[HoldingSummary],
    diagnostics: &serde_json::Value,
) -> Vec<String> {
    let mut warnings = Vec::new();
    if fills.is_empty() {
        warnings.push("本次回测没有产生任何成交。".to_string());
    }
    if holdings.is_empty() {
        warnings.push("期末没有持仓。".to_string());
    }
    let selected_after_ma_is_empty = diagnostics
        .get("selected_after_ma")
        .and_then(|v| v.as_i64())
        .unwrap_or(0)
        == 0;
    if selected_after_ma_is_empty && fills.is_empty() && holdings.is_empty() {
        warnings
            .push("最终没有股票通过完整选股链路，结果为空时请优先查看 diagnostics。".to_string());
    }
    if diagnostics
        .get("market_cap_missing_count")
        .and_then(|v| v.as_i64())
        .unwrap_or(0)
        > 0
    {
        warnings.push("存在 market_cap 缺失或非正值，当前会直接阻断该股票进入候选池。".to_string());
    }
    warnings
}

fn print_summary(
    summary: &RunSummary,
    equity_curve: &[DailyEquityPoint],
    holdings: &[HoldingSummary],
) {
    if equity_curve.is_empty() {
        println!("No equity curve points generated.");
        return;
    }

    println!("Strategy: {}", summary.strategy);
    println!("Start equity: {:.2}", summary.start_equity);
    println!("Final equity: {:.2}", summary.final_equity);
    println!("Total return: {:.2}%", summary.total_return * 100.0);
    println!("Trades: {}", summary.trade_count);
    println!("Final holdings: {}", summary.holding_count);

    if let (Some(code), Some(close)) = (&summary.benchmark_code, summary.benchmark_last_close) {
        println!("Benchmark last close: {} {:.2}", code, close);
    }

    println!("Recent equity points:");
    for point in equity_curve
        .iter()
        .rev()
        .take(3)
        .collect::<Vec<_>>()
        .into_iter()
        .rev()
    {
        println!(
            "  {} equity {:.2} cash {:.2} mv {:.2}",
            point.date, point.total_equity, point.cash, point.market_value
        );
    }

    if holdings.is_empty() {
        println!("No holdings at the end of the demo run.");
    } else {
        println!("Ending holdings:");
        for holding in holdings {
            println!(
                "  {} qty {} mv {:.2} pnl {:.2}",
                holding.symbol, holding.quantity, holding.market_value, holding.unrealized_pnl
            );
        }
    }
}
