"""
ab_stats.stats 함수 테스트: proportions_ztest, ttest_ind_welch
"""

import numpy as np
import pytest
from scipy import stats as scipy_stats

from ab_stats.stats import proportions_ztest, ttest_ind_welch


# ---------------------------------------------------------------------------
# proportions_ztest
# ---------------------------------------------------------------------------

def test_proportions_ztest_basic():
    """기본: DataFrame 1행, 필수 컬럼 존재, p_value·statistic 범위"""
    df = proportions_ztest(
        control_n=1000,
        control_success=100,
        treatment_n=1000,
        treatment_success=120,
        alpha=0.05,
        power=0.8,
    )
    assert len(df) == 1
    for col in ["metric_formula", "metric_value", "delta_relative", "delta_absolute",
                "p_value", "CI_relative", "CI_absolute", "MSS", "statistic"]:
        assert col in df.columns

    assert 0 <= df["p_value"].iloc[0] <= 1
    assert df["metric_formula"].iloc[0] == "120/1000"
    assert abs(df["metric_value"].iloc[0] - 0.12) < 1e-10
    assert abs(df["delta_absolute"].iloc[0] - 0.02) < 1e-10


def test_proportions_ztest_same_proportion():
    """비율이 같으면 p_value가 크고, delta_absolute=0"""
    df = proportions_ztest(
        control_n=500,
        control_success=100,
        treatment_n=500,
        treatment_success=100,
    )
    assert abs(df["delta_absolute"].iloc[0]) < 1e-10
    assert df["p_value"].iloc[0] >= 0.99  # 동일 비율이면 p ≈ 1


def test_proportions_ztest_invalid_n():
    """관측수 0 이하면 ValueError"""
    with pytest.raises(ValueError, match="must be positive"):
        proportions_ztest(0, 0, 100, 50)
    with pytest.raises(ValueError, match="must be positive"):
        proportions_ztest(100, 50, 0, 0)


def test_proportions_ztest_invalid_success():
    """성공수 범위 벗어나면 ValueError"""
    with pytest.raises(ValueError, match="Success counts"):
        proportions_ztest(100, 101, 100, 50)
    with pytest.raises(ValueError, match="Success counts"):
        proportions_ztest(100, -1, 100, 50)


# ---------------------------------------------------------------------------
# ttest_ind_welch
# ---------------------------------------------------------------------------

def test_ttest_ind_welch_basic():
    """기본: DataFrame 1행, 필수 컬럼, Welch t와 scipy 결과와 일치 (100개 이상 샘플)"""
    np.random.seed(42)
    n = 150  # 100개 이상 샘플로 실제에 가깝게
    control = np.random.normal(10, 2, n)
    treatment = np.random.normal(12, 2, n)

    df = ttest_ind_welch(control.tolist(), treatment.tolist(), alpha=0.05, power=0.8)

    assert len(df) == 1
    for col in ["metric_formula", "metric_value", "delta_relative", "delta_absolute",
                "p_value", "CI_relative", "CI_absolute", "MSS", "statistic", "df"]:
        assert col in df.columns

    # scipy Welch t-test와 통계량·p-value 비교 (동일 데이터)
    t_scipy, p_scipy = scipy_stats.ttest_ind(treatment, control, equal_var=False)
    # statistic은 소수 둘째 자리로 반올림되어 출력되므로 허용 오차 0.01
    assert abs(df["statistic"].iloc[0] - t_scipy) < 0.01
    assert abs(df["p_value"].iloc[0] - p_scipy) < 1e-5

    assert 0 <= df["p_value"].iloc[0] <= 1
    assert df["metric_value"].iloc[0] == pytest.approx(np.mean(treatment), rel=1e-10)


def test_ttest_ind_welch_metric_formula():
    """metric_formula = 실험군 누적값/실험군 관측수"""
    control = [1.0, 2.0, 3.0]
    treatment = [0, 0, 0, 200, 500]  # sum=700, n=5
    df = ttest_ind_welch(control, treatment)
    assert df["metric_formula"].iloc[0] == "700/5"
    assert df["metric_value"].iloc[0] == pytest.approx(700 / 5, rel=1e-10)


def test_ttest_ind_welch_too_few_observations():
    """그룹당 2미만이면 ValueError"""
    with pytest.raises(ValueError, match="at least 2 observations"):
        ttest_ind_welch([1.0], [1.0, 2.0])
    with pytest.raises(ValueError, match="at least 2 observations"):
        ttest_ind_welch([1.0, 2.0], [3.0])


def test_ttest_ind_welch_constant_groups():
    """두 그룹 모두 상수(분산 0)면 p_value가 NaN, CI_absolute에 nan 포함"""
    control = [5.0, 5.0, 5.0]
    treatment = [7.0, 7.0, 7.0]
    df = ttest_ind_welch(control, treatment)
    assert np.isnan(df["p_value"].iloc[0])
    assert "nan" in df["CI_absolute"].iloc[0].lower()


def test_ttest_ind_welch_nan_removal():
    """NaN이 있으면 제거 후 계산 (listwise deletion)"""
    control = [1.0, 2.0, 3.0, np.nan]
    treatment = [2.0, 3.0, 4.0]
    # control은 3개만 사용되어야 함
    df = ttest_ind_welch(control, treatment)
    assert len(df) == 1
    assert np.isfinite(df["p_value"].iloc[0]) or df["p_value"].iloc[0] is not None
