"""Automated Research & Safe Model Retraining Pipeline."""
from __future__ import annotations

import time
from typing import Any, Sequence

from backend.app.features.pipeline import feature_pipeline
from backend.app.models.classifier import LogisticRegressionBaseline
from backend.app.models.registry import model_registry
from backend.app.schemas.market import Candle


class RetrainingPipeline:
    """Orchestrates historical dataset creation, model fitting, calibration, and challenger registration."""

    def train_candidate(
        self,
        candles: Sequence[Candle],
        version_tag: str = "",
    ) -> dict[str, Any]:
        if len(candles) < 100:
            return {"success": False, "reason": "Insufficient candles for training (<100)"}

        # 1. Build causal feature rows and labels
        X: list[list[float]] = []
        y: list[int] = []

        # Target horizon = 5 bars forward (zero leakage: label is determined by future, features exclusively past)
        horizon = 5
        for i in range(35, len(candles) - horizon):
            hist = candles[: i + 1]
            feat = feature_pipeline.extract_features(hist)
            if not feat.get("valid", False):
                continue

            # Normalized feature vector
            row = [
                feat["rsi_14"] / 100.0,
                feat["macd_hist"],
                feat["ema_9_diff"] * 100.0,
                feat["bb_bandwidth"] * 100.0,
                feat["volume_ratio"],
            ]
            X.append(row)

            # Binary label: Did future close exceed entry price?
            future_close = float(candles[i + horizon].close)
            curr_close = float(candles[i].close)
            label = 1 if future_close > curr_close else 0
            y.append(label)

        # 2. Chronological Train / Out-of-Sample Validation Split (80% / 20%)
        split_idx = int(len(X) * 0.8)
        X_train, X_val = X[:split_idx], X[split_idx:]
        y_train, y_val = y[:split_idx], y[split_idx:]

        # 3. Train Model
        model = LogisticRegressionBaseline(l2_penalty=0.05, lr=0.02, n_epochs=150)
        model.fit(X_train, y_train)

        # 4. Out-of-Sample Evaluation
        eval_metrics = model.evaluate(X_val, y_val)

        # 5. Register Challenger
        v_tag = version_tag or f"challenger_{int(time.time())}"
        model_id = model_registry.register_challenger(
            version=v_tag,
            algorithm="LogisticRegression_Calibrated_OOS",
            brier_score=eval_metrics.brier_score,
            val_accuracy=eval_metrics.accuracy,
            metrics={
                "val_accuracy": eval_metrics.accuracy,
                "brier_score": eval_metrics.brier_score,
                "log_loss": eval_metrics.log_loss,
                "n_samples": len(X),
                "val_samples": len(X_val),
            },
            artifact_path=f"models/{v_tag}.joblib",
        )

        return {
            "success": True,
            "model_id": model_id,
            "version": v_tag,
            "val_accuracy": eval_metrics.accuracy,
            "brier_score": eval_metrics.brier_score,
            "calibration_curve": eval_metrics.calibration_bins,
            "status": "CHALLENGER (Requires explicit admin promotion to Champion)",
        }


retraining_pipeline = RetrainingPipeline()
