"""
Assembles the DB-independent inputs for `compute_confidence_score()`/`compute_data_quality_score()`
from data `app/workers/recompute.py` already holds in memory (the metrics dict, the 5 pillar
`ScoreResult`s, the `FinancialSnapshot`, the blended DCF/multiples fair values) — kept separate and
pure/unit-testable so the "which metrics count as computed", "how many pillars are present", "how
much history is available" logic has real test coverage of its own, not just the two scoring
engines (`confidence.py`/`data_quality.py`) it feeds into (StockLab overhaul, Part A1: prior to
this pass, both engines existed with unit tests but were never actually called from `recompute.py`
— this module is the missing link between them and real recompute-time data).

Source-tier data (OFFICIAL_FILING/PRIMARY/SECONDARY/CALCULATED) is deliberately NOT assembled
here — it requires a database query (`FinancialPeriod.source_id` -> `Source.provider_tier`) and is
fetched by `recompute.py`'s own `_source_tiers_for_security()` instead; this module only prepares
the pieces that need no I/O, so it can be tested without a database.
"""
from __future__ import annotations

from typing import Optional

from app.engines.types import Applicability, DataQualityStatus, MetricResult


def pillar_completeness(pillar_values: list[Optional[float]]) -> tuple[int, int]:
    """(pillars_present, pillars_total) from a list of the 5 pillar scores (None where a pillar
    itself could not be computed, e.g. compute_competitive_advantage_score({}) with no proxies)."""
    total = len(pillar_values)
    present = sum(1 for v in pillar_values if v is not None)
    return present, total


def metric_completeness_inputs(metrics: dict[str, MetricResult]) -> tuple[list[DataQualityStatus], int]:
    """(statuses of metrics that produced a meaningful value, count of the real gaps among the rest).

    Mirrors `MetricResult.is_meaningful`. A metric correctly marked NOT_MEANINGFUL for this
    security's business type (e.g. dividend yield for a company that has never paid one) is
    excluded from the missing-count entirely and deliberately: it is not "missing data" in the
    Confidence Score's sense, it is a correct N/M determination by the Industry Applicability
    Matrix — counting it against confidence would penalize a bank for not having a P/E-relevant
    inventory turnover ratio, which is exactly the kind of category error this codebase's N/M
    discipline elsewhere (docs/AUDIT_METRICS.md) exists to prevent. Only INSUFFICIENT_DATA/MISSING
    outcomes (a real data gap, not a business-model mismatch) count toward the missing total.
    """
    meaningful_statuses = [m.status for m in metrics.values() if m.is_meaningful]
    real_gaps = sum(
        1 for m in metrics.values()
        if not m.is_meaningful and m.applicability != Applicability.NOT_MEANINGFUL
    )
    return meaningful_statuses, real_gaps


def years_of_history(history_periods_count: int) -> int:
    """Trailing years actually available: the current period plus every prior period the
    FinancialSnapshot carried (`FinancialSnapshot.history`, most-recent-first, one entry per
    fiscal year in this build's annual-only data basis — see docs/AUDIT_METRICS.md's TTM finding
    for why this is years, not quarters)."""
    return history_periods_count + 1


def average_ignoring_none(values: list[Optional[float]]) -> Optional[float]:
    """Same semantics as `app/engines/valuation/blend.py::_average_ignoring_none` (private there,
    duplicated here rather than imported across module boundaries that shouldn't know about each
    other) — used to reduce a multi-multiple fair-value dict to one comparison point for the
    Confidence Score's DCF-vs-multiples agreement component."""
    clean = [v for v in values if v is not None]
    if not clean:
        return None
    return sum(clean) / len(clean)
