"""Hard Filter Engine — pre-pipeline gating for the dual-pipeline signal engine.

Evaluates macro bias, valuation score, and earnings proximity to short-circuit
both pipelines before evaluation.  All conditions are checked and all triggered
reasons are collected (no short-circuit on first match).

Requirements: 4.1, 4.2, 4.3, 4.4, 4.5, 4.6
"""

from __future__ import annotations

import logging
from dataclasses import dataclass, field

from services.signal_engine.config import HardFilterConfig
from services.signal_engine.models import NormalizedInput

logger = logging.getLogger(__name__)


@dataclass
class HardFilterResult:
    """Outcome of the hard filter evaluation.

    ``filtered=True`` means the ticker should be **skipped** — both pipelines
    are short-circuited.  ``reasons`` lists every filter that triggered.
    """

    filtered: bool = False
    reasons: list[str] = field(default_factory=list)


def evaluate_hard_filters(
    normalized: NormalizedInput,
    config: HardFilterConfig,
) -> HardFilterResult:
    """Evaluate pre-pipeline hard filters.

    Checks (all evaluated, not short-circuited):
    - ``macro_bias == config.macro_bias_skip`` → reason ``"macro_bias_negative"``
    - ``valuation_score < config.valuation_min`` → reason ``"valuation_below_threshold"``
    - ``earnings_proximity_days <= config.earnings_days`` → reason ``"earnings_block"``

    Missing optional fields (``valuation_score is None``,
    ``earnings_proximity_days is None``) do **not** trigger a filter — missing
    data should not produce a false-positive SKIP.

    Returns a :class:`HardFilterResult` with ``filtered=True`` when at least
    one reason was recorded.
    """
    reasons: list[str] = []

    # 4.1 — macro_bias exact equality with configured skip value
    if normalized.macro_bias == config.macro_bias_skip:
        reasons.append("macro_bias_negative")

    # 4.2 — valuation score below minimum threshold
    if (
        normalized.valuation_score is not None
        and normalized.valuation_score < config.valuation_min
    ):
        reasons.append("valuation_below_threshold")

    # 4.3 — earnings proximity within block window
    if (
        normalized.earnings_proximity_days is not None
        and normalized.earnings_proximity_days <= config.earnings_days
    ):
        reasons.append("earnings_block")

    filtered = len(reasons) > 0

    if filtered:
        logger.info(
            "Hard filter triggered for %s: %s",
            normalized.ticker,
            ", ".join(reasons),
        )

    return HardFilterResult(filtered=filtered, reasons=reasons)
