"""PancIsletMass v0 — cohort statistics.

control vs HFD vs drug ANOVA + pairwise t-test (Bonferroni).
scipy 없으면 numpy fallback.
"""

from __future__ import annotations

from dataclasses import dataclass
from itertools import combinations
from typing import Dict, List, Tuple

import numpy as np

try:
    from scipy import stats as scistats  # type: ignore

    _HAS_SCIPY = True
except Exception:  # pragma: no cover
    _HAS_SCIPY = False


@dataclass
class GroupStat:
    group: str
    n: int
    mean: float
    sd: float
    sem: float


def group_stats(values_by_group: Dict[str, List[float]]) -> List[GroupStat]:
    out: List[GroupStat] = []
    for g, vals in values_by_group.items():
        arr = np.asarray(vals, dtype=float)
        if arr.size == 0:
            out.append(GroupStat(group=g, n=0, mean=0.0, sd=0.0, sem=0.0))
            continue
        sd = float(arr.std(ddof=1)) if arr.size > 1 else 0.0
        sem = sd / np.sqrt(arr.size) if arr.size > 1 else 0.0
        out.append(GroupStat(group=g, n=int(arr.size), mean=float(arr.mean()), sd=sd, sem=float(sem)))
    return out


def one_way_anova(values_by_group: Dict[str, List[float]]) -> Tuple[float, float]:
    """Returns (F, p). Falls back to numpy if scipy missing."""
    arrays = [np.asarray(v, dtype=float) for v in values_by_group.values() if len(v) > 0]
    if len(arrays) < 2 or any(a.size < 2 for a in arrays):
        return float("nan"), float("nan")
    if _HAS_SCIPY:
        F, p = scistats.f_oneway(*arrays)
        return float(F), float(p)
    # numpy fallback
    grand = np.concatenate(arrays)
    grand_mean = grand.mean()
    ss_between = sum(a.size * (a.mean() - grand_mean) ** 2 for a in arrays)
    ss_within = sum(((a - a.mean()) ** 2).sum() for a in arrays)
    df_b = len(arrays) - 1
    df_w = grand.size - len(arrays)
    if df_w <= 0 or ss_within <= 0:
        return float("nan"), float("nan")
    F = (ss_between / df_b) / (ss_within / df_w)
    # p-value approximation via beta distribution would need scipy; return NaN
    return float(F), float("nan")


def pairwise_ttests(
    values_by_group: Dict[str, List[float]],
    bonferroni: bool = True,
) -> List[Dict]:
    """Pairwise Welch's t-test. p adjusted by Bonferroni if requested."""
    rows: List[Dict] = []
    keys = list(values_by_group.keys())
    pairs = list(combinations(keys, 2))
    n_pairs = max(len(pairs), 1)
    for a_key, b_key in pairs:
        a = np.asarray(values_by_group[a_key], dtype=float)
        b = np.asarray(values_by_group[b_key], dtype=float)
        if a.size < 2 or b.size < 2:
            rows.append({"a": a_key, "b": b_key, "t": float("nan"), "p_raw": float("nan"), "p_adj": float("nan")})
            continue
        if _HAS_SCIPY:
            t, p = scistats.ttest_ind(a, b, equal_var=False)
            t = float(t); p = float(p)
        else:
            # Welch's t numpy fallback
            ma, mb = a.mean(), b.mean()
            va, vb = a.var(ddof=1), b.var(ddof=1)
            denom = np.sqrt(va / a.size + vb / b.size)
            t = float((ma - mb) / denom) if denom > 0 else float("nan")
            p = float("nan")
        p_adj = float(min(p * n_pairs, 1.0)) if (bonferroni and not np.isnan(p)) else p
        rows.append({"a": a_key, "b": b_key, "t": t, "p_raw": p, "p_adj": p_adj})
    return rows
