"""Signal Quality Analysis: Score-bucket monotonicity check.

Does higher composite score predict better forward returns?

Reads snapshot parquet, computes compute_entry_score for each row,
buckets into quintiles, and checks Spearman rank correlation between
score bucket and mean forward return.

Usage:
  python -m dev.analysis.signal_quality_analysis \
    --snapshot-dir data/datasets/snapshots/snapshot_2026_03_20 \
    --split train [--output-dir ./data/analysis]
"""
from __future__ import annotations

import argparse
import csv
import statistics
from pathlib import Path
from typing import Any

import pyarrow.parquet as pq

from libs.backtest.scoring import compute_entry_score


def _load_rows(snapshot_dir: Path, split: str) -> list[dict[str, Any]]:
    """Load parquet snapshot into list of dicts."""
    parquet_path = snapshot_dir / f"{split}.parquet"
    table = pq.read_table(parquet_path)
    return table.to_pylist()


def _bucket_label(score: float) -> str:
    """Assign score to a quintile bucket."""
    if score < 0.2:
        return "0.0-0.2"
    if score < 0.4:
        return "0.2-0.4"
    if score < 0.6:
        return "0.4-0.6"
    if score < 0.8:
        return "0.6-0.8"
    return "0.8-1.0"


BUCKET_ORDER = ["0.0-0.2", "0.2-0.4", "0.4-0.6", "0.6-0.8", "0.8-1.0"]
FWD_HORIZONS = ["fwd_return_1d", "fwd_return_3d", "fwd_return_5d", "fwd_return_10d"]


def analyze(rows: list[dict[str, Any]], output_dir: Path) -> None:
    """Score all rows, bucket, compute statistics, write output."""
    output_dir.mkdir(parents=True, exist_ok=True)

    # Score each row and assign bucket
    scored: list[dict[str, Any]] = []
    for row in rows:
        score = compute_entry_score(row)
        bucket = _bucket_label(score)
        scored.append({"score": score, "bucket": bucket, **row})

    # Group by bucket
    by_bucket: dict[str, list[dict[str, Any]]] = {b: [] for b in BUCKET_ORDER}
    for s in scored:
        by_bucket[s["bucket"]].append(s)

    # Compute per-bucket stats
    csv_rows: list[dict[str, Any]] = []
    for bucket in BUCKET_ORDER:
        items = by_bucket[bucket]
        n = len(items)
        row_out: dict[str, Any] = {"bucket": bucket, "count": n}

        for horizon in FWD_HORIZONS:
            vals = [float(r[horizon]) for r in items if r.get(horizon) is not None]
            if vals:
                row_out[f"{horizon}_mean"] = statistics.mean(vals)
                row_out[f"{horizon}_win_rate"] = sum(1 for v in vals if v > 0) / len(vals)
            else:
                row_out[f"{horizon}_mean"] = None
                row_out[f"{horizon}_win_rate"] = None

        csv_rows.append(row_out)

    # Spearman rank correlation (bucket rank vs mean return)
    spearman_results: dict[str, float | None] = {}
    for horizon in FWD_HORIZONS:
        means = [r[f"{horizon}_mean"] for r in csv_rows if r[f"{horizon}_mean"] is not None]
        if len(means) >= 3:
            # Rank correlation: do the means increase with bucket rank?
            ranks_score = list(range(len(means)))
            ranks_return = _rank(means)
            spearman_results[horizon] = _spearman(ranks_score, ranks_return)
        else:
            spearman_results[horizon] = None

    # Write CSV
    csv_path = output_dir / "signal_quality_analysis.csv"
    if csv_rows:
        fieldnames = list(csv_rows[0].keys())
        with open(csv_path, "w", newline="") as f:
            writer = csv.DictWriter(f, fieldnames=fieldnames)
            writer.writeheader()
            writer.writerows(csv_rows)

    # Console output
    print(f"\n{'='*80}")
    print(f"Signal Quality Analysis — {len(scored)} events")
    print(f"{'='*80}")
    print(f"\n{'Bucket':<12} {'Count':>6}", end="")
    for h in FWD_HORIZONS:
        label = h.replace("fwd_return_", "")
        print(f"  {label+'_mean':>10} {label+'_wr':>8}", end="")
    print()
    print("-" * 80)

    for row_out in csv_rows:
        print(f"{row_out['bucket']:<12} {row_out['count']:>6}", end="")
        for h in FWD_HORIZONS:
            mean_val = row_out[f"{h}_mean"]
            wr_val = row_out[f"{h}_win_rate"]
            mean_str = f"{mean_val:+.4f}" if mean_val is not None else "    N/A"
            wr_str = f"{wr_val:.1%}" if wr_val is not None else "  N/A"
            print(f"  {mean_str:>10} {wr_str:>8}", end="")
        print()

    print(f"\nSpearman rank correlation (bucket vs mean return):")
    for h in FWD_HORIZONS:
        label = h.replace("fwd_return_", "")
        val = spearman_results.get(h)
        print(f"  {label}: {val:+.3f}" if val is not None else f"  {label}: N/A")

    print(f"\nCSV written to: {csv_path}")


def _rank(values: list[float]) -> list[float]:
    """Compute ranks (0-indexed) for a list of values."""
    indexed = sorted(enumerate(values), key=lambda x: x[1])
    ranks = [0.0] * len(values)
    for rank, (idx, _) in enumerate(indexed):
        ranks[idx] = float(rank)
    return ranks


def _spearman(x: list[float], y: list[float]) -> float:
    """Compute Spearman rank correlation coefficient."""
    n = len(x)
    if n < 2:
        return 0.0
    d_sq = sum((xi - yi) ** 2 for xi, yi in zip(x, y))
    return 1 - (6 * d_sq) / (n * (n**2 - 1))


def main() -> None:
    parser = argparse.ArgumentParser(description="Signal Quality Analysis")
    parser.add_argument("--snapshot-dir", required=True, help="Path to snapshot directory")
    parser.add_argument("--split", default="train", help="Split name (train/valid/test)")
    parser.add_argument("--output-dir", default="./data/analysis", help="Output directory")
    args = parser.parse_args()

    rows = _load_rows(Path(args.snapshot_dir), args.split)
    if not rows:
        print("No data found.")
        return

    analyze(rows, Path(args.output_dir))


if __name__ == "__main__":
    main()
