"""
The five 0-100 sub-scores (spec §18-22 / docs/SCORING.md §2). Each is an (optionally weighted)
average of the industry-relative percentiles of a fixed metric list. A metric that is
NOT_MEANINGFUL for the company's sector, or MISSING, is excluded from both the company's average
and the peer set — never defaulted to a low/zero score.
"""
from __future__ import annotations

from dataclasses import dataclass, field
from typing import Optional

from app.engines.scoring.percentile import percentile_rank, peer_values_with_fallback
from app.engines.types import MetricResult

# metric_key -> lower_is_better
QUALITY_METRICS = {
    "roic": False, "roic_minus_wacc": False, "gross_margin": False, "operating_margin": False,
    "net_margin": False, "fcf_margin": False, "fcf_growth_cagr_3y": False,
    "revenue_growth_cagr_3y": False, "eps_growth_cagr_3y": False,
    "share_count_growth_cagr_3y": True,  # shrinking share count is good
}

FINANCIAL_HEALTH_METRICS = {
    "debt_to_ebitda": True, "net_debt_to_ebitda": True, "debt_to_equity": True,
    "interest_coverage": False, "current_ratio": False, "fcf": False,
}

GROWTH_METRICS = {
    "revenue_growth_cagr_3y": False, "revenue_cagr_5y": False, "eps_growth_cagr_3y": False,
    "fcf_growth_cagr_3y": False, "revenue_growth_trend": False, "fcf_growth_trend": False,
}

COMPETITIVE_ADVANTAGE_METRICS = {
    # Persistence proxies use a pre-computed stability score (0-100, higher = more persistent)
    # produced by engines/scoring/persistence.py — see compute_competitive_advantage below.
}

VALUATION_METRICS = {
    "pe": True, "forward_pe": True, "peg": True, "ev_to_ebitda": True, "p_fcf": True,
    "ev_to_fcf": True, "fcf_yield": False,
}


@dataclass
class ScoreResult:
    value: Optional[float]
    metrics_included: list[str] = field(default_factory=list)
    metrics_excluded: list[str] = field(default_factory=list)
    peer_group_tier: Optional[str] = None
    status: str = "CALCULATED"


def _meaningful(metrics: dict[str, MetricResult], key: str) -> bool:
    m = metrics.get(key)
    return m is not None and m.is_meaningful


def compute_generic_subscore(
    metric_defs: dict[str, bool],
    company_metrics: dict[str, MetricResult],
    peer_metric_values: dict[str, dict[str, list[float]]],
    # peer_metric_values[metric_key] = {"industry_bucket": [...], "industry": [...], "sector": [...]}
) -> ScoreResult:
    included, excluded, percentiles = [], [], []
    tier_used = None
    for key, lower_is_better in metric_defs.items():
        if not _meaningful(company_metrics, key):
            excluded.append(key)
            continue
        peers_by_tier = peer_metric_values.get(key, {})
        peers, tier = peer_values_with_fallback(
            peers_by_tier.get("industry_bucket", []),
            peers_by_tier.get("industry", []),
            peers_by_tier.get("sector", []),
        )
        # AUDIT FIX (StockLab final engineering pass, Part B2 -- docs/AUDIT_PEER_GROUPS_B2.md).
        # `percentile_rank()` returns a neutral 50.0 for an EMPTY peer list, which meant a metric
        # scored against zero real comparisons was indistinguishable from one scored against a
        # full peer group: same status, same plausible-looking number. Combined with
        # recompute_security() never actually supplying peer_metric_values, that made every
        # security's every pillar score exactly 50. The percentile arithmetic is not changed --
        # the fix is that a metric with no peers at ANY tier is now EXCLUDED from the subscore,
        # exactly as a metric with no value of its own already is, so `metrics_excluded` reports
        # it and the subscore is computed only from metrics that had something to compare against.
        # This was the fix proposed in docs/AUDIT_PROVIDER_RESILIENCE.md's fixture-validation
        # finding; it is applied here now that real peer groups exist to make it meaningful.
        if not peers:
            excluded.append(key)
            continue
        pct = percentile_rank(company_metrics[key].value, peers, lower_is_better=lower_is_better)
        percentiles.append(pct)
        included.append(key)
        tier_used = tier  # last one wins for reporting; all metrics normally share a peer universe

    if not included:
        return ScoreResult(value=None, metrics_excluded=excluded, status="INSUFFICIENT_DATA")
    return ScoreResult(
        value=sum(percentiles) / len(percentiles),
        metrics_included=included, metrics_excluded=excluded,
        peer_group_tier=tier_used,
    )


def compute_quality_score(company_metrics, peer_metric_values) -> ScoreResult:
    return compute_generic_subscore(QUALITY_METRICS, company_metrics, peer_metric_values)


def compute_financial_health_score(company_metrics, peer_metric_values) -> ScoreResult:
    return compute_generic_subscore(FINANCIAL_HEALTH_METRICS, company_metrics, peer_metric_values)


def compute_growth_score(company_metrics, peer_metric_values) -> ScoreResult:
    return compute_generic_subscore(GROWTH_METRICS, company_metrics, peer_metric_values)


def compute_valuation_score(company_metrics, peer_metric_values) -> ScoreResult:
    return compute_generic_subscore(VALUATION_METRICS, company_metrics, peer_metric_values)


MIN_PROXIES_FOR_COMPETITIVE_ADVANTAGE = 3


def compute_competitive_advantage_score(
    proxy_values: dict[str, Optional[float]],
) -> ScoreResult:
    """
    Measurable moat proxies only (spec §21) — never an invented "moat score". `proxy_values` is
    produced by engines/scoring/persistence.py (ROIC/margin/revenue/FCF persistence, each already
    expressed 0-100 where higher = more persistent/stable) plus, when disclosed, concentration
    and asset-intensity proxies. Proxies the provider doesn't disclose are simply absent from the
    dict — never defaulted — and if fewer than MIN_PROXIES are available the score is
    INSUFFICIENT_DATA rather than computed from 1-2 noisy signals (spec: "не измисляй score").
    """
    available = {k: v for k, v in proxy_values.items() if v is not None}
    if len(available) < MIN_PROXIES_FOR_COMPETITIVE_ADVANTAGE:
        return ScoreResult(
            value=None, metrics_included=list(available), status="INSUFFICIENT_DATA",
            metrics_excluded=[k for k, v in proxy_values.items() if v is None],
        )
    return ScoreResult(
        value=sum(available.values()) / len(available),
        metrics_included=list(available),
        metrics_excluded=[k for k, v in proxy_values.items() if v is None],
    )
