"""Conformal interval trainer for target-delta forecasts."""

import re
import time
from pathlib import Path

import joblib
import numpy as np
from sklearn.ensemble import GradientBoostingRegressor

from .data_loader import load_target_outcomes
from .train_direction import ALL_FEATURE_KEYS


def _horizon_to_minutes(horizon: str) -> float:
    total = 0.0
    for amount, unit in re.findall(r"(\d+)(mo|y|w|d|h|m)", str(horizon or "").strip().lower()):
        quantity = float(amount or 0)
        if unit == "m":
            total += quantity
        elif unit == "h":
            total += quantity * 60
        elif unit == "d":
            total += quantity * 1440
        elif unit == "w":
            total += quantity * 10080
        elif unit == "mo":
            total += quantity * 43200
        elif unit == "y":
            total += quantity * 525600
    return total if total > 0 else 240.0


class ConformalIntervalTrainer:
    model_name = "interval_conformal_calibrator"
    artifact_path = Path("models") / "interval_conformal_calibrator.joblib"

    def run(self, days: int = 180) -> dict:
        start = time.time()
        outcomes = load_target_outcomes(days)
        if len(outcomes) < 60:
            return {
                "model": self.model_name,
                "status": "skipped",
                "metrics": {"samples": len(outcomes)},
                "duration_seconds": round(time.time() - start, 2),
                "artifact_path": "",
                "error": f"Need at least 60 samples, got {len(outcomes)}",
            }

        rows = []
        for outcome in outcomes:
            features = dict(outcome["features"])
            features["probability_hint"] = float(outcome.get("probability", 0.5))
            features["horizon_minutes"] = _horizon_to_minutes(str(outcome.get("horizon", "4h")))
            rows.append(features)

        feature_keys = ALL_FEATURE_KEYS + ["probability_hint", "horizon_minutes"]
        X = np.array([[row.get(key, 0.0) for key in feature_keys] for row in rows], dtype=np.float32)
        y = np.array([float(outcome["changePct"]) for outcome in outcomes], dtype=np.float32)

        n = len(X)
        train_end = int(n * 0.70)
        cal_end = int(n * 0.85)
        X_train, X_cal = X[:train_end], X[train_end:cal_end]
        y_train, y_cal = y[:train_end], y[train_end:cal_end]
        X_test, y_test = X[cal_end:], y[cal_end:]

        base = GradientBoostingRegressor(
            loss="absolute_error", n_estimators=250, max_depth=3, random_state=42
        )
        base.fit(X_train, y_train)

        cal_preds = base.predict(X_cal)
        residuals = y_cal - cal_preds
        lower = float(np.quantile(residuals, 0.10))
        upper = float(np.quantile(residuals, 0.90))

        test_preds = base.predict(X_test)
        coverage = float(np.mean((y_test >= test_preds + lower) & (y_test <= test_preds + upper)))

        self.artifact_path.parent.mkdir(parents=True, exist_ok=True)
        joblib.dump(
            {
                "lower_residual_pct": lower,
                "upper_residual_pct": upper,
                "coverage": coverage,
                "trained_at": str(int(time.time())),
            },
            self.artifact_path,
        )

        return {
            "model": self.model_name,
            "status": "success",
            "metrics": {
                "lower_residual_pct": lower,
                "upper_residual_pct": upper,
                "coverage": coverage,
                "train_samples": len(X_train),
                "calibration_samples": len(X_cal),
                "test_samples": len(X_test),
            },
            "duration_seconds": round(time.time() - start, 2),
            "artifact_path": str(self.artifact_path),
        }
