"""
Builds a real `SellTriggerInputs` from historical metric series (StockLab overhaul, Part 17).

AUDIT FINDING this pass was built to fix: `app/workers/recompute.py` called
`compute_recommendation(..., sell_trigger_inputs=SellTriggerInputs())` — an all-default,
all-`False` instance — meaning every deterioration-based sell trigger was permanently disabled in
the running system regardless of a security's actual history. `evaluate_sell_triggers()` itself
was correct and tested; nothing ever fed it real data. This module is the real feed.

Dependency-free like every other engine module — takes plain historical value lists (most-recent-
first, i.e. `series[0]` = current period), not a DB session. `app/workers/recompute.py` is
responsible for querying `metric_history` and calling this.

**Known limitation, inherited from the TTM/point-in-time finding in `docs/AUDIT_METRICS.md`:**
this codebase's `metric_history` is currently populated from ANNUAL filings only (the FMP adapter
defaults to `period="annual"`; no quarterly ingestion exists). The trigger field names
(`roic_declining_3q`, etc.) describe quarterly cadence per the original spec, but this builder can
only evaluate them against consecutive ANNUAL periods until quarterly ingestion exists. Documented
here rather than silently computing "3q" triggers off annual data without saying so — a trigger
firing from this builder means "declined for 3 consecutive annual periods", not literally 3
quarters, until quarterly ingestion is built.
"""
from __future__ import annotations

from typing import Optional, Sequence

from app.engines.recommendation.sell_triggers import SellTriggerInputs


def _monotonic_decline(series: Sequence[Optional[float]], periods: int) -> bool:
    """True if the most `periods` recent values are all present and each is strictly lower than
    the one before it (i.e. a decline every period, not just net-lower over the window) — the
    stricter and more defensible reading of "declining N periods" vs. merely "lower than N
    periods ago", since a single big drop followed by flat periods would trip the looser
    definition without actually being a persistent deterioration trend."""
    window = list(series[:periods])
    if len(window) < periods or any(v is None for v in window):
        return False
    return all(window[i] < window[i + 1] for i in range(periods - 1))


def _monotonic_rise(series: Sequence[Optional[float]], periods: int) -> bool:
    window = list(series[:periods])
    if len(window) < periods or any(v is None for v in window):
        return False
    return all(window[i] > window[i + 1] for i in range(periods - 1))


def build_sell_trigger_inputs(
    *,
    price: Optional[float] = None,
    overvalued_price: Optional[float] = None,
    roic_history: Sequence[Optional[float]] = (),
    operating_margin_history: Sequence[Optional[float]] = (),
    fcf_history: Sequence[Optional[float]] = (),
    revenue_history: Sequence[Optional[float]] = (),
    eps_history: Sequence[Optional[float]] = (),
    net_debt_to_ebitda_history: Sequence[Optional[float]] = (),
    interest_coverage_history: Sequence[Optional[float]] = (),
    diluted_shares_history: Sequence[Optional[float]] = (),
    dividend_event_history: Sequence[Optional[str]] = (),
) -> SellTriggerInputs:
    """Every `*_history` argument is most-recent-first (index 0 = current period). Guidance
    reduction and consensus-estimate-revision triggers are NOT computed here — this codebase does
    not ingest forward guidance or analyst-estimate-revision history (see
    docs/SPEC_COVERAGE.md's estimate-revisions known limitation) — they are left at their
    dataclass default of `False` rather than guessed at.
    """
    price_above_overvalued = (
        price is not None and overvalued_price is not None and price > overvalued_price
    )

    dividend_cut = bool(dividend_event_history) and dividend_event_history[0] == "DIVIDEND_CUT"

    diluted_shares_up_over_3pct = False
    if len(diluted_shares_history) > 1 and diluted_shares_history[0] is not None and diluted_shares_history[1]:
        prior = diluted_shares_history[1]
        if prior:
            diluted_shares_up_over_3pct = (diluted_shares_history[0] / prior - 1) > 0.03

    return SellTriggerInputs(
        price_above_overvalued_band=price_above_overvalued,
        roic_declining_3q=_monotonic_decline(roic_history, 3),
        operating_margin_declining_3q=_monotonic_decline(operating_margin_history, 3),
        fcf_declining_3q=_monotonic_decline(fcf_history, 3),
        revenue_declining_2q=_monotonic_decline(revenue_history, 2),
        eps_declining_2q=_monotonic_decline(eps_history, 2),
        net_debt_to_ebitda_rising_3q=_monotonic_rise(net_debt_to_ebitda_history, 3),
        interest_coverage_declining_3q=_monotonic_decline(interest_coverage_history, 3),
        dividend_cut=dividend_cut,
        diluted_shares_up_over_3pct_yoy=diluted_shares_up_over_3pct,
        guidance_reduced=False,
        consensus_estimate_down_over_5pct_90d=False,
    )
