"""
Streamlined run statistics with zero-reason classifier
"""
from dataclasses import dataclass, field, asdict
from datetime import datetime
from typing import Dict, List, Optional
import math
import json

@dataclass
class RunStats:
    """Lightweight statistics tracking for matching runs"""
    # Timing (optional, keep if you want durations later)
    start_time: datetime = field(default_factory=datetime.utcnow)
    end_time: Optional[datetime] = None

    # Core counters
    total_sources: int = 0
    candidate_pairs: int = 0
    pairs_after_blocking: int = 0
    unmatched_sources: int = 0

    # Scores
    max_score_observed: float = 0.0
    scores_sample: List[float] = field(default_factory=list)

    # Extras
    fields_present_hist: Dict[int, int] = field(default_factory=dict)
    zero_reason: Optional[str] = None  # "no_candidates"|"all_below_threshold"|"blocked_out"|None
    blocking_coverage: float = 0.0
    soft_widen_blocks: int = 0

    def update_scores(self, scores: List[float], sample_cap: int = 2000) -> None:
        """Track score samples for percentile calculation"""
        if not scores:
            return
        self.max_score_observed = max(self.max_score_observed, max(scores))
        if len(self.scores_sample) < sample_cap:
            self.scores_sample.extend(scores[:max(0, sample_cap - len(self.scores_sample))])

    def finalize_zero_reason(self, threshold: float) -> None:
        """Classify why zero matches occurred"""
        if self.candidate_pairs == 0:
            self.zero_reason = "no_candidates"
        elif self.pairs_after_blocking == 0 and self.candidate_pairs > 0:
            self.zero_reason = "blocked_out"
        elif self.max_score_observed < threshold:
            self.zero_reason = "all_below_threshold"
        else:
            self.zero_reason = None

    def p95(self) -> float:
        """Calculate 95th percentile of sampled scores"""
        if not self.scores_sample:
            return 0.0
        xs = sorted(self.scores_sample)
        idx = min(len(xs) - 1, math.ceil(0.95 * (len(xs) - 1)))
        return xs[idx]

    def one_line(self) -> str:
        """Generate one-line summary"""
        unmatched_pct = (self.unmatched_sources / self.total_sources * 100) if self.total_sources else 0.0
        return (f"sources={self.total_sources} cand={self.candidate_pairs} "
                f"post_block={self.pairs_after_blocking} unmatched={unmatched_pct:.1f}% "
                f"max={self.max_score_observed:.2f} p95={self.p95():.2f} "
                f"zero_reason={self.zero_reason or '-'}")

    def to_dict(self) -> dict:
        """Convert stats to dictionary for serialization"""
        d = asdict(self)
        # Convert datetime fields to ISO format if present
        if self.start_time:
            d['start_time'] = self.start_time.isoformat()
        if self.end_time:
            d['end_time'] = self.end_time.isoformat()
        return d

    def to_json(self) -> str:
        """Convert stats to JSON string"""
        return json.dumps(self.to_dict(), indent=2)


# Backward compatibility shim
class StatsCollector(RunStats):
    """Legacy alias for RunStats"""
    pass
