"""XGBoost direction classifier with walk-forward temporal validation.

Trains horizon-specific models (scalp/standard/position) to predict
price direction (up/down/sideways) from technical + on-chain features.
Uses proper time-based splits to avoid look-ahead bias.
"""

import os
from pathlib import Path

import numpy as np

MODEL_DIR = Path(os.getenv("MODEL_DIR", "models"))

# Feature keys matching FeatureVector from Node.js side + engineered features
BASE_FEATURE_KEYS = [
    "rsi", "macdHistogram", "bollingerPercentB", "ema12", "ema26",
    "atr", "obv", "fundingRate", "fearGreed", "priceChange24h",
    "rsiSlope", "volumeRatio", "emaCrossoverPct", "atrPct",
]

# Engineered features added during preprocessing
ENGINEERED_KEYS = [
    "lagReturn1", "lagReturn3", "lagReturn5",
    "rollingVolatility5", "rollingVolatility10",
    "priceVsSMA20",
]

ALL_FEATURE_KEYS = BASE_FEATURE_KEYS + ENGINEERED_KEYS

# Horizon profiles and their direction thresholds (% move to label up/down)
HORIZON_PROFILES = {
    "scalp": {"horizons": ["5m", "15m", "30m"], "threshold": 0.15},
    "standard": {"horizons": ["1h", "4h"], "threshold": 0.35},
    "position": {"horizons": ["1d", "7d"], "threshold": 0.75},
}


def engineer_features(features: dict, history: list[dict] | None = None) -> dict:
    """Add lagged returns, rolling volatility, price vs SMA to raw features."""
    result = dict(features)

    if history and len(history) >= 5:
        prices = [h.get("price", 0) for h in history[-20:]]
        if len(prices) >= 2 and prices[-1] > 0:
            result["lagReturn1"] = (prices[-1] - prices[-2]) / prices[-1] * 100 if prices[-2] > 0 else 0
        else:
            result["lagReturn1"] = 0

        if len(prices) >= 4 and prices[-1] > 0:
            result["lagReturn3"] = (prices[-1] - prices[-4]) / prices[-1] * 100 if prices[-4] > 0 else 0
        else:
            result["lagReturn3"] = 0

        if len(prices) >= 6 and prices[-1] > 0:
            result["lagReturn5"] = (prices[-1] - prices[-6]) / prices[-1] * 100 if prices[-6] > 0 else 0
        else:
            result["lagReturn5"] = 0

        if len(prices) >= 5:
            returns = [
                (prices[i] - prices[i - 1]) / prices[i - 1] * 100
                for i in range(1, len(prices))
                if prices[i - 1] > 0
            ]
            result["rollingVolatility5"] = float(np.std(returns[-5:])) if len(returns) >= 5 else 0
            result["rollingVolatility10"] = float(np.std(returns[-10:])) if len(returns) >= 10 else 0
        else:
            result["rollingVolatility5"] = 0
            result["rollingVolatility10"] = 0

        if len(prices) >= 20:
            sma20 = np.mean(prices[-20:])
            result["priceVsSMA20"] = (prices[-1] - sma20) / sma20 * 100 if sma20 > 0 else 0
        else:
            result["priceVsSMA20"] = 0
    else:
        for key in ENGINEERED_KEYS:
            result.setdefault(key, 0)

    return result


class DirectionTrainer:
    """Walk-forward XGBoost trainer for direction prediction."""

    def __init__(self, profile: str = "standard"):
        if profile not in HORIZON_PROFILES:
            raise ValueError(f"Unknown profile: {profile}. Use: {list(HORIZON_PROFILES.keys())}")
        self.profile = profile
        self.threshold = HORIZON_PROFILES[profile]["threshold"]

    def label_direction(self, change_pct: float) -> int:
        """Convert price change to direction label: 0=down, 1=sideways, 2=up."""
        if change_pct > self.threshold:
            return 2  # up
        elif change_pct < -self.threshold:
            return 0  # down
        return 1  # sideways

    def run(self, outcomes: list[dict] | None = None) -> dict:
        """Train XGBoost model from prediction outcomes.

        Each outcome should have:
          - features: dict of feature values
          - changePct: actual price change percentage
          - horizon: string horizon label

        Returns training result dict.
        """
        import time
        start = time.time()

        if not outcomes or len(outcomes) < 30:
            return {
                "model": f"xgb-direction-{self.profile}",
                "status": "skipped",
                "metrics": None,
                "duration_seconds": time.time() - start,
                "artifact_path": "",
                "error": f"Need at least 30 samples, got {len(outcomes) if outcomes else 0}",
            }

        try:
            import xgboost as xgb
            from sklearn.metrics import accuracy_score, classification_report
        except ImportError:
            return {
                "model": f"xgb-direction-{self.profile}",
                "status": "failed",
                "metrics": None,
                "duration_seconds": time.time() - start,
                "artifact_path": "",
                "error": "xgboost not installed",
            }

        # Filter outcomes for this profile's horizons
        valid_horizons = set(HORIZON_PROFILES[self.profile]["horizons"])
        filtered = [o for o in outcomes if o.get("horizon") in valid_horizons]
        if len(filtered) < 30:
            filtered = outcomes  # Fall back to all outcomes if not enough per-profile

        # Build feature matrix and labels
        X = np.array([
            [o.get("features", {}).get(k, 0) for k in ALL_FEATURE_KEYS]
            for o in filtered
        ])
        y = np.array([self.label_direction(o.get("changePct", 0)) for o in filtered])

        # Walk-forward temporal split: 70% train, 15% val, 15% test
        n = len(X)
        train_end = int(n * 0.70)
        val_end = int(n * 0.85)

        X_train, y_train = X[:train_end], y[:train_end]
        X_val, y_val = X[train_end:val_end], y[train_end:val_end]
        X_test, y_test = X[val_end:], y[val_end:]

        if len(X_test) < 5:
            return {
                "model": f"xgb-direction-{self.profile}",
                "status": "skipped",
                "metrics": None,
                "duration_seconds": time.time() - start,
                "artifact_path": "",
                "error": "Not enough data for temporal split",
            }

        # Train XGBoost with early stopping
        model = xgb.XGBClassifier(
            n_estimators=200,
            max_depth=4,
            learning_rate=0.05,
            subsample=0.8,
            colsample_bytree=0.8,
            min_child_weight=3,
            reg_alpha=0.1,
            reg_lambda=1.0,
            objective="multi:softprob",
            num_class=3,
            eval_metric="mlogloss",
            early_stopping_rounds=20,
            random_state=42,
        )

        model.fit(
            X_train, y_train,
            eval_set=[(X_val, y_val)],
            verbose=False,
        )

        # Evaluate on test set
        y_pred = model.predict(X_test)
        accuracy = float(accuracy_score(y_test, y_pred))

        # Save model
        MODEL_DIR.mkdir(parents=True, exist_ok=True)
        model_path = MODEL_DIR / f"xgb_direction_{self.profile}.json"
        model.save_model(str(model_path))

        duration = time.time() - start

        return {
            "model": f"xgb-direction-{self.profile}",
            "status": "success",
            "metrics": {
                "accuracy": round(accuracy, 4),
                "train_samples": len(X_train),
                "val_samples": len(X_val),
                "test_samples": len(X_test),
                "best_iteration": model.best_iteration if hasattr(model, "best_iteration") else None,
            },
            "duration_seconds": round(duration, 2),
            "artifact_path": str(model_path),
        }
