"""Statistical tests for the 8 hypotheses."""

from __future__ import annotations

from typing import Dict, List

import numpy as np
from scipy import stats


def wilcoxon_paired(
    scores_a: List[float],
    scores_b: List[float],
) -> Dict[str, float]:
    """Wilcoxon signed-rank test (paired, non-parametric).

    Returns: {"statistic": W, "p_value": p, "direction": "A>B"|"B>A"|"equal"}
    """
    stat, p = stats.wilcoxon(scores_a, scores_b, alternative="two-sided")
    mean_diff = float(np.mean(np.array(scores_a) - np.array(scores_b)))
    direction = "A>B" if mean_diff > 0 else ("B>A" if mean_diff < 0 else "equal")
    return {"statistic": float(stat), "p_value": float(p), "direction": direction}


def cliffs_delta(
    scores_a: List[float],
    scores_b: List[float],
) -> Dict[str, float]:
    """Cliff's delta effect size (robust for ordinal data).

    Thresholds: |d| < 0.147 = negligible, < 0.33 = small, < 0.474 = medium, else large
    """
    a, b = np.array(scores_a), np.array(scores_b)
    n_a, n_b = len(a), len(b)

    more = sum(1 for ai in a for bi in b if ai > bi)
    less = sum(1 for ai in a for bi in b if ai < bi)
    d = (more - less) / (n_a * n_b)

    abs_d = abs(d)
    if abs_d < 0.147:
        interp = "negligible"
    elif abs_d < 0.33:
        interp = "small"
    elif abs_d < 0.474:
        interp = "medium"
    else:
        interp = "large"

    return {"delta": float(d), "interpretation": interp}


def bonferroni_correction(p_values: List[float]) -> List[float]:
    """Apply Bonferroni correction for multiple comparisons."""
    k = len(p_values)
    return [min(p * k, 1.0) for p in p_values]


def coefficient_of_variation(scores: List[float]) -> float:
    """CV = std/mean — measures consistency (lower = more consistent)."""
    arr = np.array(scores)
    mean = arr.mean()
    if mean == 0:
        return float("inf")
    return float(arr.std() / mean)


def test_all_hypotheses(
    condition_scores: Dict[str, Dict[str, List[float]]],
) -> Dict[str, Dict]:
    """Run all applicable hypotheses based on which conditions are present.

    Legacy H1-H6 (SCAFFOLD_FULL era) run only when those conditions exist.
    New H11-H14 (LLM_SCAFFOLD era) run when LLM_SCAFFOLD is present.

    *condition_scores*: ``{condition_id: {metric_name: [scores_per_text]}}``
    """
    results: Dict[str, Dict] = {}

    has_full = "SCAFFOLD_FULL" in condition_scores
    has_partial = "SCAFFOLD_PARTIAL" in condition_scores
    has_vocab = "VOCAB_ONLY" in condition_scores
    has_naked = "NAKED" in condition_scores
    has_twostep = "TWOSTEP" in condition_scores
    has_llm_scaffold = "LLM_SCAFFOLD" in condition_scores

    naked = condition_scores.get("NAKED", {})
    vocab = condition_scores.get("VOCAB_ONLY", {})
    full = condition_scores.get("SCAFFOLD_FULL", {})
    partial = condition_scores.get("SCAFFOLD_PARTIAL", {})
    twostep = condition_scores.get("TWOSTEP", {})
    llm_scaffold = condition_scores.get("LLM_SCAFFOLD", {})

    # ── Legacy hypotheses (Run 1-5, require SCAFFOLD_FULL) ────────

    if has_full and has_naked and has_vocab:
        # H1: FULL covers more lenses than NAKED and VOCAB
        results["H1"] = {
            "full_vs_naked": wilcoxon_paired(full["lens_coverage"], naked["lens_coverage"]),
            "full_vs_vocab": wilcoxon_paired(full["lens_coverage"], vocab["lens_coverage"]),
            "effect_fn": cliffs_delta(full["lens_coverage"], naked["lens_coverage"]),
            "effect_fv": cliffs_delta(full["lens_coverage"], vocab["lens_coverage"]),
        }

        # H2: FULL has higher ADS than VOCAB
        results["H2"] = {
            "full_vs_vocab": wilcoxon_paired(full["ads"], vocab["ads"]),
            "effect": cliffs_delta(full["ads"], vocab["ads"]),
        }

        # H4: FULL has more discoveries
        results["H4"] = {
            "full_vs_naked": wilcoxon_paired(full["discovery"], naked["discovery"]),
            "full_vs_vocab": wilcoxon_paired(full["discovery"], vocab["discovery"]),
        }

    if has_full and has_naked and has_vocab and has_partial:
        # H3: FULL has higher CLIS than all
        results["H3"] = {
            "full_vs_naked": wilcoxon_paired(full["clis"], naked["clis"]),
            "full_vs_vocab": wilcoxon_paired(full["clis"], vocab["clis"]),
            "full_vs_partial": wilcoxon_paired(full["clis"], partial["clis"]),
        }

        # H5: FULL has lower variance (CV)
        results["H5"] = {
            "cv_full": coefficient_of_variation(full["ads"]),
            "cv_naked": coefficient_of_variation(naked["ads"]),
            "cv_vocab": coefficient_of_variation(vocab["ads"]),
            "cv_partial": coefficient_of_variation(partial["ads"]),
        }

        # H6: Dose-response monotonicity
        means = {
            "NAKED": float(np.mean(naked["ads"])),
            "VOCAB_ONLY": float(np.mean(vocab["ads"])),
            "SCAFFOLD_PARTIAL": float(np.mean(partial["ads"])),
            "SCAFFOLD_FULL": float(np.mean(full["ads"])),
        }
        expected_order = ["NAKED", "VOCAB_ONLY", "SCAFFOLD_PARTIAL", "SCAFFOLD_FULL"]
        is_monotonic = all(
            means[expected_order[i]] <= means[expected_order[i + 1]]
            for i in range(len(expected_order) - 1)
        )
        results["H6"] = {"means": means, "is_monotonic": is_monotonic}

    # H9/H10: TWOSTEP tests (Run 4-5)
    if has_twostep and has_vocab and has_full:
        results["H9"] = {
            "twostep_vs_vocab_discovery": wilcoxon_paired(
                twostep["discovery"], vocab["discovery"],
            ),
            "twostep_vs_full_discovery": wilcoxon_paired(
                twostep["discovery"], full["discovery"],
            ),
            "effect_tv": cliffs_delta(twostep["discovery"], vocab["discovery"]),
            "effect_tf": cliffs_delta(twostep["discovery"], full["discovery"]),
        }

        results["H10"] = {
            "twostep_vs_full_ads": wilcoxon_paired(
                twostep["ads"], full["ads"],
            ),
            "twostep_vs_vocab_ads": wilcoxon_paired(
                twostep["ads"], vocab["ads"],
            ),
            "effect_tf": cliffs_delta(twostep["ads"], full["ads"]),
            "effect_tv": cliffs_delta(twostep["ads"], vocab["ads"]),
        }

    # ── New hypotheses (Run 6+, LLM_SCAFFOLD) ────────────────────

    if has_llm_scaffold and has_vocab:
        # H11: LLM_SCAFFOLD vs VOCAB_ONLY — overall quality (ADS)
        results["H11"] = {
            "description": "LLM_SCAFFOLD vs VOCAB_ONLY overall quality",
            "ads": wilcoxon_paired(llm_scaffold["ads"], vocab["ads"]),
            "ads_effect": cliffs_delta(llm_scaffold["ads"], vocab["ads"]),
            "clis": wilcoxon_paired(llm_scaffold["clis"], vocab["clis"]),
            "clis_effect": cliffs_delta(llm_scaffold["clis"], vocab["clis"]),
        }

        # H12: LLM_SCAFFOLD vs VOCAB_ONLY — discovery
        results["H12"] = {
            "description": "LLM_SCAFFOLD vs VOCAB_ONLY discovery",
            "discovery": wilcoxon_paired(
                llm_scaffold["discovery"], vocab["discovery"],
            ),
            "discovery_effect": cliffs_delta(
                llm_scaffold["discovery"], vocab["discovery"],
            ),
            "error_prevention": wilcoxon_paired(
                llm_scaffold["error_prevention"],
                vocab["error_prevention"],
            ),
        }

    if has_llm_scaffold and has_naked:
        # H13: LLM_SCAFFOLD vs NAKED — does LLM scaffold beat baseline?
        results["H13"] = {
            "description": "LLM_SCAFFOLD vs NAKED baseline",
            "ads": wilcoxon_paired(llm_scaffold["ads"], naked["ads"]),
            "ads_effect": cliffs_delta(llm_scaffold["ads"], naked["ads"]),
            "discovery": wilcoxon_paired(
                llm_scaffold["discovery"], naked["discovery"],
            ),
            "lens_coverage": wilcoxon_paired(
                llm_scaffold["lens_coverage"], naked["lens_coverage"],
            ),
        }

    if has_llm_scaffold and has_vocab and has_naked:
        # H14: Consistency comparison (CV) across all 3 conditions
        results["H14"] = {
            "description": "Consistency (CV) comparison",
            "cv_naked": coefficient_of_variation(naked["ads"]),
            "cv_vocab": coefficient_of_variation(vocab["ads"]),
            "cv_llm_scaffold": coefficient_of_variation(llm_scaffold["ads"]),
        }

    return results
