"""CLI tool: report on ReviewFeedback data collected for ML training readiness."""

from __future__ import annotations

import argparse
import sys
from datetime import UTC
from datetime import datetime
from datetime import timedelta

from sqlalchemy import func
from sqlalchemy import select
from sqlalchemy.orm import Session

from fenliu.database import get_db
from fenliu.models import HashtagStream
from fenliu.models import ReviewFeedback

# Snapshot columns added in v0.7.0 — rows from before that release may have NULLs.
SNAPSHOT_COLUMNS: list[str] = [
    "content_snippet",
    "spam_score_at_review",
    "hashtag_count",
    "hashtags_snapshot",
    "attachment_count",
    "has_video",
    "boosts",
    "likes",
    "replies",
    "author_is_bot",
    "instance",
    "stream_id",
]

# ML readiness thresholds — from ML_INTEGRATION_GUIDE.md discussion notes.
MIN_REVIEWS: int = 500
PREFERRED_REVIEWS: int = 1000
MIN_DATA_DAYS: int = 180
MIN_COMPLETENESS_PCT: float = 90.0
MAX_IMBALANCE_PCT: float = 85.0


def _pct(part: int, total: int) -> float:
    """Return percentage rounded to one decimal, or 0.0 if total is zero."""
    return round(part / total * 100, 1) if total else 0.0


def collect_stats(db: Session, since: datetime | None = None) -> dict:
    """Query the database and return a stats dict for report rendering.

    Args:
        db: Active SQLAlchemy session.
        since: If provided, only rows created on or after this datetime are counted.

    Returns:
        Dictionary with keys: total, decisions, data_days, oldest, newest,
        weekly_rate, completeness, completeness_pct, top_instances, streams.

    """

    def _base(stmt):
        """Apply the since-filter if set."""
        return stmt.where(ReviewFeedback.created_at >= since) if since else stmt

    # Decision counts
    decision_rows = db.execute(
        _base(select(ReviewFeedback.decision, func.count().label("n")).group_by(ReviewFeedback.decision))
    ).all()
    decisions: dict[str, int] = {r.decision: r.n for r in decision_rows}
    total: int = sum(decisions.values())

    # Date range
    date_row = db.execute(
        _base(
            select(
                func.min(ReviewFeedback.created_at).label("oldest"),
                func.max(ReviewFeedback.created_at).label("newest"),
            )
        )
    ).one()
    oldest: datetime | None = date_row.oldest
    newest: datetime | None = date_row.newest

    if oldest and newest and total:
        data_days = max(1, (newest - oldest).days)
        weekly_rate = round(total / (data_days / 7), 1)
    else:
        data_days = 0
        weekly_rate = 0.0

    # Snapshot completeness — count non-NULL values for each snapshot column.
    completeness: dict[str, int] = {}
    for col_name in SNAPSHOT_COLUMNS:
        col = getattr(ReviewFeedback, col_name)
        count = db.scalar(_base(select(func.count()).select_from(ReviewFeedback).where(col.is_not(None)))) or 0
        completeness[col_name] = count

    # Overall completeness = worst column (weakest link).
    min_complete = min(completeness.values()) if total else 0
    completeness_pct = _pct(min_complete, total) if total else 0.0

    # Top 10 instances in rejected posts.
    instance_rows = db.execute(
        _base(
            select(ReviewFeedback.instance, func.count().label("n"))
            .where(ReviewFeedback.decision == "rejected")
            .where(ReviewFeedback.instance.is_not(None))
            .group_by(ReviewFeedback.instance)
            .order_by(func.count().desc())
            .limit(10)
        )
    ).all()
    top_instances: list[tuple[str, int]] = [(r.instance, r.n) for r in instance_rows]

    # Per-stream breakdown — approved/rejected counts grouped by stream_id.
    stream_rows = db.execute(
        _base(
            select(
                ReviewFeedback.stream_id,
                ReviewFeedback.decision,
                func.count().label("n"),
            )
            .where(ReviewFeedback.stream_id.is_not(None))
            .group_by(ReviewFeedback.stream_id, ReviewFeedback.decision)
        )
    ).all()

    streams_raw: dict[int, dict[str, int]] = {}
    stream_ids: set[int] = set()
    for r in stream_rows:
        streams_raw.setdefault(r.stream_id, {})[r.decision] = r.n
        stream_ids.add(r.stream_id)

    stream_names: dict[int, str] = {}
    if stream_ids:
        sn_rows = db.execute(
            select(HashtagStream.id, HashtagStream.hashtag, HashtagStream.instance).where(
                HashtagStream.id.in_(stream_ids)
            )
        ).all()
        for sr in sn_rows:
            stream_names[sr.id] = f"#{sr.hashtag} ({sr.instance})"

    streams: list[tuple[str, dict[str, int]]] = [
        (stream_names.get(sid, f"stream:{sid}"), counts) for sid, counts in sorted(streams_raw.items())
    ]

    return {
        "total": total,
        "decisions": decisions,
        "data_days": data_days,
        "oldest": oldest,
        "newest": newest,
        "weekly_rate": weekly_rate,
        "completeness": completeness,
        "completeness_pct": completeness_pct,
        "top_instances": top_instances,
        "streams": streams,
    }


def _check_line(label: str, ok: bool, detail: str = "") -> str:
    """Format a single readiness checklist line."""
    mark = "✓" if ok else "✗"
    suffix = f"  ({detail})" if detail else ""
    return f"  [{mark}] {label}{suffix}"


def _print_summary(total: int, decisions: dict[str, int]) -> None:
    """Print the summary section."""
    print()
    print("Summary")
    print("-" * 50)
    print(f"  Total:             {total:>8,}")
    for decision in ("approved", "rejected", "score_adjusted"):
        n = decisions.get(decision, 0)
        print(f"  {decision:<18} {n:>7,}  ({_pct(n, total):.1f}%)")


def _print_date_range(stats: dict) -> None:
    """Print the date range and collection rate section."""
    print()
    print("Date Range & Collection Rate")
    print("-" * 50)
    if stats["oldest"]:
        print(f"  Oldest entry:    {stats['oldest'].strftime('%Y-%m-%d')}")
        print(f"  Newest entry:    {stats['newest'].strftime('%Y-%m-%d')}")
        print(f"  Days of data:    {stats['data_days']}")
        print(f"  Avg/week:        {stats['weekly_rate']:,.1f} reviews")
    else:
        print("  No data.")


def _print_completeness(stats: dict, total: int) -> None:
    """Print the snapshot completeness section."""
    print()
    print("Snapshot Completeness  (non-NULL rows per column)")
    print("-" * 50)
    for col, count in stats["completeness"].items():
        pct = _pct(count, total)
        marker = "✓" if pct >= MIN_COMPLETENESS_PCT else "!"
        print(f"  {marker} {col:<25} {count:>7,} / {total:>7,}  ({pct:.1f}%)")


def _print_top_instances(stats: dict) -> None:
    """Print the top instances in rejected posts section."""
    print()
    print("Top Instances in Rejected Posts")
    print("-" * 50)
    if stats["top_instances"]:
        for instance, n in stats["top_instances"]:
            print(f"  {instance:<40} {n:>5,}")
    else:
        print("  No rejected posts with instance data.")


def _print_streams(stats: dict) -> None:
    """Print the per-stream breakdown section."""
    print()
    print("Per-Stream Breakdown")
    print("-" * 50)
    if stats["streams"]:
        for name, counts in stats["streams"]:
            approved = counts.get("approved", 0)
            rejected = counts.get("rejected", 0)
            adjusted = counts.get("score_adjusted", 0)
            print(f"  {name}")
            print(f"    approved={approved:,}  rejected={rejected:,}  adjusted={adjusted:,}")
    else:
        print("  No stream data.")


def _print_readiness(stats: dict, decisions: dict[str, int], total: int) -> None:
    """Print the ML readiness assessment checklist."""
    print()
    print("ML Readiness Assessment")
    print("-" * 50)
    approved = decisions.get("approved", 0)
    rejected = decisions.get("rejected", 0)
    dominant_pct = _pct(max(approved, rejected), total) if total else 0.0
    balanced = dominant_pct <= MAX_IMBALANCE_PCT
    days_short = max(0, MIN_DATA_DAYS - stats["data_days"])

    checks: list[tuple[str, bool, str]] = [
        (f"Reviews ≥ {MIN_REVIEWS:,} (minimum)", total >= MIN_REVIEWS, f"have {total:,}"),
        (f"Reviews ≥ {PREFERRED_REVIEWS:,} (preferred)", total >= PREFERRED_REVIEWS, f"have {total:,}"),
        (
            f"Days of data ≥ {MIN_DATA_DAYS} (6 months)",
            stats["data_days"] >= MIN_DATA_DAYS,
            f"have {stats['data_days']} — need {days_short} more" if days_short else f"have {stats['data_days']}",
        ),
        (
            f"Snapshot completeness ≥ {MIN_COMPLETENESS_PCT:.0f}%",
            stats["completeness_pct"] >= MIN_COMPLETENESS_PCT,
            f"{stats['completeness_pct']:.1f}%",
        ),
        (
            f"Class balance (neither side >{MAX_IMBALANCE_PCT:.0f}%)",
            balanced,
            f"approved {_pct(approved, total):.0f}% / rejected {_pct(rejected, total):.0f}%",
        ),
    ]

    passed = sum(1 for _, ok, _ in checks if ok)
    for label, ok, detail in checks:
        print(_check_line(label, ok, detail))

    print()
    verdict = "READY" if passed == len(checks) else "NOT READY"
    print(f"  Overall: {verdict}  ({passed}/{len(checks)} criteria met)")
    print()


def print_report(stats: dict, period_label: str = "all time") -> None:
    """Print the human-readable stats report to stdout.

    Args:
        stats: Dict returned by :func:`collect_stats`.
        period_label: Human-readable description of the time window.

    """
    total = stats["total"]
    decisions = stats["decisions"]

    print()
    print("FenLiu ReviewFeedback Stats")
    print("=" * 50)
    print(f"Period: {period_label}")

    _print_summary(total, decisions)
    _print_date_range(stats)
    _print_completeness(stats, total)
    _print_top_instances(stats)
    _print_streams(stats)
    _print_readiness(stats, decisions, total)


def main() -> None:
    """Entry point for the fenliu-feedback-stats command."""
    parser = argparse.ArgumentParser(
        prog="fenliu-feedback-stats",
        description="Report on ReviewFeedback data to assess ML training readiness.",
    )
    parser.add_argument(
        "--days",
        type=int,
        default=None,
        metavar="N",
        help="Limit report to the last N days (default: all time)",
    )
    args = parser.parse_args()

    since: datetime | None = None
    period_label = "all time"
    if args.days is not None:
        since = datetime.now(UTC) - timedelta(days=args.days)
        period_label = f"last {args.days} day{'s' if args.days != 1 else ''}"

    try:
        with get_db() as db:
            stats = collect_stats(db, since)
    except Exception as exc:
        print(f"Error: could not connect to database: {exc}", file=sys.stderr)
        sys.exit(1)

    print_report(stats, period_label)
