"""Impact output integration — connects impact predictions to legacy consumers.

Provides the ImpactPrediction Pydantic model, compatibility adapter mapping,
feature flag for v3 mode, and comparison metrics placeholder.

Design reference: Section I & K in design.md.
Requirement 12.1, 12.7, 12.9.
"""

from __future__ import annotations

import logging
import os
from datetime import datetime, timezone
from typing import Literal

from pydantic import BaseModel, Field

logger = logging.getLogger(__name__)


# ---------------------------------------------------------------------------
# Feature flags
# ---------------------------------------------------------------------------


class ImpactPipelineConfig(BaseModel):
    """Configuration for the impact pipeline integration.

    Controls whether generative impact/novelty/confidence are removed
    from aggregation inputs when v3 mode is active.
    """

    v3_mode_enabled: bool = Field(
        default=False,
        description="When True, removes generative impact/novelty/confidence from aggregation inputs.",
    )
    use_trained_model: bool = Field(
        default=False,
        description="When True, uses trained model if approved. Otherwise uses deterministic baseline.",
    )
    legacy_compatibility: bool = Field(
        default=True,
        description="When True, maps impact predictions to legacy impact_score/impact_horizon.",
    )
    comparison_metrics_enabled: bool = Field(
        default=False,
        description="When True, stores comparison metrics between v3 and generative predictions.",
    )


def get_impact_config() -> ImpactPipelineConfig:
    """Get impact pipeline configuration from environment."""
    return ImpactPipelineConfig(
        v3_mode_enabled=os.environ.get("IMPACT_V3_MODE_ENABLED", "false").lower() == "true",
        use_trained_model=os.environ.get("IMPACT_USE_TRAINED_MODEL", "false").lower() == "true",
        legacy_compatibility=os.environ.get("IMPACT_LEGACY_COMPATIBILITY", "true").lower() == "true",
        comparison_metrics_enabled=os.environ.get("IMPACT_COMPARISON_METRICS", "false").lower() == "true",
    )


# ---------------------------------------------------------------------------
# Impact prediction output model (Pydantic)
# ---------------------------------------------------------------------------


class DirectionProbabilities(BaseModel):
    """Probability distribution over market direction outcomes."""

    positive: float = Field(ge=0.0, le=1.0, default=0.0)
    negative: float = Field(ge=0.0, le=1.0, default=0.0)
    neutral: float = Field(ge=0.0, le=1.0, default=0.0)


class HorizonProbabilities(BaseModel):
    """Probability distribution over impact horizons."""

    intraday: float = Field(ge=0.0, le=1.0, default=0.0)
    one_day: float = Field(ge=0.0, le=1.0, default=0.0)
    seven_day: float = Field(ge=0.0, le=1.0, default=0.0)
    thirty_day: float = Field(ge=0.0, le=1.0, default=0.0)
    ninety_day: float = Field(ge=0.0, le=1.0, default=0.0)


class ImpactPredictionOutput(BaseModel):
    """Complete impact prediction output for persistence and downstream use.

    Contains the full probability distributions, not just point estimates.
    This is richer than the legacy scalar fields.
    """

    direction_probs: DirectionProbabilities = Field(
        description="Probability distribution over market direction.",
    )
    expected_magnitude: float = Field(
        ge=0.0,
        description="Expected absolute magnitude of market response.",
    )
    signed_magnitude: float = Field(
        description="Direction-weighted expected magnitude.",
    )
    horizon_probs: HorizonProbabilities = Field(
        description="Probability distribution over response horizons.",
    )
    uncertainty: float = Field(
        ge=0.0,
        le=1.0,
        description="Model uncertainty (higher = less confident).",
    )
    model_source: str = Field(
        description="Which model produced this prediction.",
    )
    feature_snapshot_id: str | None = Field(
        default=None,
        description="ID of the immutable feature snapshot used for this prediction.",
    )
    prediction_time: datetime = Field(
        default_factory=lambda: datetime.now(tz=timezone.utc),
    )


# ---------------------------------------------------------------------------
# Legacy compatibility adapter
# ---------------------------------------------------------------------------


class LegacyImpactMapping(BaseModel):
    """Mapping from v3 impact prediction to legacy impact_score/impact_horizon."""

    impact_score: float = Field(
        ge=-1.0,
        le=1.0,
        description="Legacy impact score mapped from v3 signed magnitude.",
    )
    impact_horizon: Literal["intraday", "1d", "7d", "30d", "90d"] = Field(
        description="Legacy horizon mapped from most probable v3 horizon.",
    )
    mapping_version: str = "1.0.0"


def map_to_legacy_impact(prediction: ImpactPredictionOutput) -> LegacyImpactMapping:
    """Map a v3 impact prediction to legacy impact_score and impact_horizon.

    Parameters
    ----------
    prediction
        The full v3 impact prediction output.

    Returns
    -------
    LegacyImpactMapping
        Legacy-compatible fields for downstream consumers.
    """
    # impact_score: clamp signed_magnitude to [-1, 1]
    impact_score = max(-1.0, min(1.0, prediction.signed_magnitude))

    # impact_horizon: argmax of horizon probabilities
    horizon_map: dict[str, float] = {
        "intraday": prediction.horizon_probs.intraday,
        "1d": prediction.horizon_probs.one_day,
        "7d": prediction.horizon_probs.seven_day,
        "30d": prediction.horizon_probs.thirty_day,
        "90d": prediction.horizon_probs.ninety_day,
    }
    impact_horizon = max(horizon_map, key=lambda k: horizon_map[k])

    return LegacyImpactMapping(
        impact_score=impact_score,
        impact_horizon=impact_horizon,
    )


# ---------------------------------------------------------------------------
# V3 mode signal filtering
# ---------------------------------------------------------------------------


def filter_generative_scores(
    signal_dict: dict,
    config: ImpactPipelineConfig | None = None,
) -> dict:
    """Remove generative impact/novelty/confidence from aggregation inputs in v3 mode.

    When v3 mode is enabled, these fields are replaced by calibrated v3 values.
    The original generative values are removed to prevent double-counting.

    Parameters
    ----------
    signal_dict
        Dictionary of signal fields from the extraction pipeline.
    config
        Pipeline configuration. Uses env-based default if None.

    Returns
    -------
    dict
        Signal dict with generative scores removed if v3 mode is active.
    """
    if config is None:
        config = get_impact_config()

    if not config.v3_mode_enabled:
        return signal_dict

    # Fields produced by generative model that v3 replaces
    generative_fields = {
        "impact_score",
        "impact_horizon",
        "novelty_score",
        "confidence",
    }

    filtered = {k: v for k, v in signal_dict.items() if k not in generative_fields}

    logger.debug(
        "V3 mode: removed generative fields %s from signal",
        generative_fields & set(signal_dict.keys()),
    )

    return filtered


# ---------------------------------------------------------------------------
# Comparison metrics (placeholder for dashboard integration)
# ---------------------------------------------------------------------------


class ComparisonMetric(BaseModel):
    """Single comparison data point between v3 prediction and realized outcome."""

    ticker: str
    event_time: datetime
    prediction_source: str
    predicted_direction: str
    predicted_magnitude: float
    predicted_horizon: str
    realized_return_1d: float | None = None
    realized_return_7d: float | None = None
    realized_return_30d: float | None = None
    direction_correct: bool | None = None
    magnitude_error: float | None = None


# In-memory store for comparison metrics (production would use database)
_COMPARISON_METRICS: list[ComparisonMetric] = []


def record_comparison_metric(metric: ComparisonMetric) -> None:
    """Record a comparison metric for later dashboard display.

    Only records if comparison metrics are enabled in config.
    """
    config = get_impact_config()
    if not config.comparison_metrics_enabled:
        return
    _COMPARISON_METRICS.append(metric)


def get_comparison_metrics(
    ticker: str | None = None,
    limit: int = 100,
) -> list[ComparisonMetric]:
    """Retrieve stored comparison metrics, optionally filtered by ticker."""
    metrics = _COMPARISON_METRICS
    if ticker:
        metrics = [m for m in metrics if m.ticker == ticker]
    return metrics[:limit]


def clear_comparison_metrics() -> None:
    """Clear all stored comparison metrics (for testing only)."""
    _COMPARISON_METRICS.clear()
