# tests/test_ground_truth.py — Binary flag tests against ground truth labels.
"""
For each pipeline, loads a TSV of (mut_id, label) pairs where label=1 means
the mutation should flag (score > threshold) and label=0 means it should not.

Reports TP, FP, TN, FN per pipeline. Run with:

    pytest tests/test_ground_truth.py -v -s

Requires real seqmat/models — not stubbable.
"""
from __future__ import annotations

import csv
from dataclasses import dataclass
from pathlib import Path

import pytest

GROUND_TRUTH_DIR = Path(__file__).parent / "ground_truth"

# Score thresholds (0-10 scale) — at or above = "flags"
THRESHOLDS = {
    "splicing": 5.0,
    "tis": 5.0,
    "folding": 2.0,
    "protein": 2.0,
    "mirna": 2.0,
}


@dataclass
class Metrics:
    tp: int = 0
    fp: int = 0
    tn: int = 0
    fn: int = 0

    @property
    def total(self) -> int:
        return self.tp + self.fp + self.tn + self.fn

    @property
    def accuracy(self) -> float:
        return (self.tp + self.tn) / self.total if self.total else 0.0

    @property
    def sensitivity(self) -> float:
        return self.tp / (self.tp + self.fn) if (self.tp + self.fn) else 0.0

    @property
    def specificity(self) -> float:
        return self.tn / (self.tn + self.fp) if (self.tn + self.fp) else 0.0

    def __str__(self) -> str:
        lines = [
            f"  TP={self.tp}  FP={self.fp}  TN={self.tn}  FN={self.fn}",
            f"  Accuracy:    {self.accuracy:.1%}",
            f"  Sensitivity: {self.sensitivity:.1%}",
            f"  Specificity: {self.specificity:.1%}",
        ]
        return "\n".join(lines)


def _load_ground_truth(pipeline: str) -> list[tuple[str, int]]:
    """Load (mut_id, label) pairs from a pipeline's TSV file."""
    path = GROUND_TRUTH_DIR / f"{pipeline}.tsv"
    if not path.exists():
        pytest.skip(f"No ground truth file: {path}")
    pairs = []
    with open(path) as f:
        reader = csv.DictReader(f, delimiter="\t")
        for row in reader:
            pairs.append((row["mut_id"].strip(), int(row["label"])))
    if not pairs:
        pytest.skip(f"Empty ground truth file: {path}")
    return pairs


def _run_pipeline(pipeline: str, pairs: list[tuple[str, int]]) -> Metrics:
    """Run mutations through the pipeline and classify against ground truth."""
    from geney import mutation_pipeline

    threshold = THRESHOLDS[pipeline]
    metrics = Metrics()
    details = []

    for mut_id, expected_label in pairs:
        # Run with only the relevant pipeline enabled
        kwargs = dict(
            run_splicing=(pipeline == "splicing"),
            run_tis=(pipeline == "tis"),
            run_folding=(pipeline == "folding"),
            run_mirna=(pipeline == "mirna"),
            run_transcription=False,
        )
        result = mutation_pipeline(mut_id, **kwargs)

        score = result.scores[pipeline].score
        predicted = 1 if score >= threshold else 0

        if predicted == 1 and expected_label == 1:
            metrics.tp += 1
            outcome = "TP"
        elif predicted == 1 and expected_label == 0:
            metrics.fp += 1
            outcome = "FP"
        elif predicted == 0 and expected_label == 0:
            metrics.tn += 1
            outcome = "TN"
        else:
            metrics.fn += 1
            outcome = "FN"

        details.append(f"  {outcome}  {mut_id}  score={score:.1f}  expected={expected_label}")

    # Print report
    print(f"\n{'=' * 60}")
    print(f"  {pipeline.upper()} (threshold={threshold})")
    print(f"{'=' * 60}")
    for d in details:
        print(d)
    print(f"{'-' * 60}")
    print(metrics)
    print()

    return metrics


# -- One test per pipeline ----------------------------------------------------

@pytest.mark.requires_seqmat
def test_splicing_ground_truth():
    pairs = _load_ground_truth("splicing")
    m = _run_pipeline("splicing", pairs)
    assert m.fn == 0, f"Splicing: {m.fn} false negatives"


@pytest.mark.requires_seqmat
def test_tis_ground_truth():
    pairs = _load_ground_truth("tis")
    m = _run_pipeline("tis", pairs)
    assert m.fn == 0, f"TIS: {m.fn} false negatives"


@pytest.mark.requires_seqmat
def test_folding_ground_truth():
    pairs = _load_ground_truth("folding")
    m = _run_pipeline("folding", pairs)
    assert m.fn == 0, f"Folding: {m.fn} false negatives"


@pytest.mark.requires_seqmat
def test_protein_ground_truth():
    pairs = _load_ground_truth("protein")
    m = _run_pipeline("protein", pairs)
    assert m.fn == 0, f"Protein: {m.fn} false negatives"


@pytest.mark.requires_seqmat
def test_mirna_ground_truth():
    pairs = _load_ground_truth("mirna")
    m = _run_pipeline("mirna", pairs)
    assert m.fn == 0, f"miRNA: {m.fn} false negatives"
