import numpy as np
import pandas as pd

import orchestrator
from ml_engine import train_and_backtest as legacy
from ml_engine.train_and_backtest import create_accumulation_target


def _frame(prices):
    return pd.DataFrame({"close": prices})


def test_accumulation_target_for_existing_row_is_invariant_to_unrelated_future_rows():
    config = {
        "timeframe": "4h",
        "target": {
            "forward_periods_4h": [1, 2, 3],
            "weights": [0.2, 0.3, 0.5],
            "return_scales_pct": [5, 10, 20],
        },
    }
    base = _frame([100, 102, 104, 106, 108, 110, 112, 114])
    extended = _frame([100, 102, 104, 106, 108, 110, 112, 114, 1000, 1, 2000])

    base_target = create_accumulation_target(base, config)
    extended_target = create_accumulation_target(extended, config)

    assert np.isclose(base_target.iloc[0], extended_target.iloc[0])
    assert 0 <= base_target.iloc[0] <= 100


def test_rolling_validation_purges_forward_label_horizon_at_train_boundaries(monkeypatch):
    rows = 200
    frame = pd.DataFrame({
        "feature": np.linspace(0, 1, rows),
        "target": np.arange(rows, dtype=float) % 100,
        "close": np.linspace(10_000, 20_000, rows),
    })
    observed = []

    def fake_train(X_train, y_train, X_val, y_val, X_test, *args):
        observed.append((len(X_train), len(X_val), len(X_test)))
        return np.full(len(X_test), 50.0), np.array([1.0])

    monkeypatch.setattr(legacy, "_train_and_predict_window", fake_train)
    config = {
        "model_type": "xgboost",
        "target": {"forward_periods_4h": [1, 2, 3]},
        "training": {
            "rolling_train_size": 120,
            "rolling_test_size": 40,
            "validation_pct": 0.25,
        },
        "features": {"use_scaler": False, "use_pca": False},
        "strategy": {},
    }

    legacy.rolling_window_train_test(frame, ["feature"], config)

    assert observed[0] == (87, 27, 40)


def test_periodic_accumulation_compares_equal_contributions_and_retains_cash():
    result = legacy.simulate_periodic_accumulation(
        predicted_scores=np.array([90, 10, 90, 10], dtype=float),
        close_prices=np.array([100, 300, 100, 200], dtype=float),
        buy_threshold=70,
        contribution=100,
    )

    assert np.isclose(result["dca_contributed"], 400)
    assert np.isclose(result["model_contributed"], 400)
    assert np.isclose(result["model_cash"], 100)
    assert np.isclose(result["model_btc"], 3)
    assert result["model_terminal_value"] > result["dca_terminal_value"]


def test_compiled_results_publish_equal_capital_terminal_wealth_metric():
    predictions = [
        {"predicted": score, "actual": 50.0, "close": price}
        for score, price in zip([90, 10, 90, 10], [100, 300, 100, 200])
    ]

    result = legacy.compile_results(
        predictions,
        per_window_cost_improvement=[],
        fi_sum=np.array([1.0]),
        fi_count=1,
        feature_cols=["feature"],
        config={"model_type": "xgboost", "strategy": {"good_buy_threshold": 70}},
    )

    assert result["terminal_wealth_improvement_pct"] > 0
    assert result["backtest_objective"] == "equal_periodic_contribution_terminal_wealth"


def test_orchestrator_selects_models_by_terminal_wealth_not_cost_basis():
    results = {
        "terminal_wealth_improvement_pct": 4.5,
        "cost_basis_improvement_pct": 99.0,
    }

    assert orchestrator.objective_score(results) == 4.5
