"""그룹 간 통계 (one-way ANOVA, Welch t-test fallback) + 요약.

scipy.stats가 있으면 우선 사용, 없으면 직접 구현한 ANOVA F 검정 사용.
"""
from __future__ import annotations

from dataclasses import dataclass
from typing import Dict, List, Sequence, Tuple

import math
import numpy as np

try:
    from scipy import stats as _scistats
    _HAS_SCIPY = True
except Exception:  # pragma: no cover
    _scistats = None
    _HAS_SCIPY = False


@dataclass
class GroupSummary:
    group: str
    n: int
    mean: float
    std: float
    median: float


@dataclass
class AnovaResult:
    f_stat: float
    p_value: float
    df_between: int
    df_within: int
    summaries: List[GroupSummary]


def summarize_groups(groups: Dict[str, Sequence[float]]) -> List[GroupSummary]:
    out = []
    for name, vals in groups.items():
        arr = np.asarray(list(vals), dtype=np.float64)
        if arr.size == 0:
            out.append(GroupSummary(name, 0, float("nan"), float("nan"), float("nan")))
            continue
        out.append(GroupSummary(
            group=name, n=int(arr.size),
            mean=float(arr.mean()),
            std=float(arr.std(ddof=1)) if arr.size > 1 else 0.0,
            median=float(np.median(arr)),
        ))
    return out


def _anova_manual(groups: Sequence[Sequence[float]]) -> Tuple[float, float, int, int]:
    """One-way ANOVA, F + p (F 분포 CDF: scipy 없으면 근사)."""
    arrays = [np.asarray(list(g), dtype=np.float64) for g in groups]
    arrays = [a for a in arrays if a.size > 0]
    if len(arrays) < 2:
        return float("nan"), float("nan"), 0, 0
    n_total = sum(a.size for a in arrays)
    grand_mean = sum(a.sum() for a in arrays) / n_total
    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 = n_total - len(arrays)
    if df_w <= 0 or ss_within <= 0:
        return float("nan"), float("nan"), df_b, max(df_w, 0)
    ms_b = ss_between / df_b
    ms_w = ss_within / df_w
    f = ms_b / ms_w if ms_w > 0 else float("nan")
    # p-value: scipy survival, else 매우 거친 근사
    if _HAS_SCIPY:
        p = float(_scistats.f.sf(f, df_b, df_w))
    else:  # pragma: no cover
        # 근사: large F → p<0.001, F>5 → p<0.05, etc.
        if math.isnan(f):
            p = float("nan")
        elif f > 10:
            p = 0.001
        elif f > 5:
            p = 0.01
        elif f > 3:
            p = 0.05
        else:
            p = 0.2
    return float(f), float(p), int(df_b), int(df_w)


def one_way_anova(groups: Dict[str, Sequence[float]]) -> AnovaResult:
    summaries = summarize_groups(groups)
    arrays = [list(v) for v in groups.values()]
    if _HAS_SCIPY:
        try:
            f, p = _scistats.f_oneway(*[np.asarray(a, dtype=np.float64) for a in arrays if len(a) > 0])
            df_b = len([a for a in arrays if len(a) > 0]) - 1
            df_w = sum(len(a) for a in arrays) - (df_b + 1)
            return AnovaResult(float(f), float(p), int(df_b), int(df_w), summaries)
        except Exception:
            pass
    f, p, df_b, df_w = _anova_manual(arrays)
    return AnovaResult(f, p, df_b, df_w, summaries)


def cohort_table(per_sample: List[Dict]) -> List[Dict]:
    """샘플별 metric 리스트 → group별 평균 + ANOVA용 그룹 dict 반환을 위한 단순 helper."""
    return per_sample
