"""CE-Gate 统计核心：截断 Beta 混合 e-process 的纯函数实现。

配对不一致检验：候选与基线跑同一题，只数翻转（基线错->候选对 = W；
基线对->候选错 = L）。H0（候选不优）下翻转方向精确五五开，
E = 2^(W+L+1)*B(W+1,L+1)*[1-I_1/2(W+1,L+1)] 为 H0 下非负上鞅，
Ville 不等式给出任意停时 P(E >= 1/alpha) <= alpha。

设计规格见 research-wiki/designs/2026-07-03-ce-gate-formal-design.md。
仅依赖 scipy.special，无 I/O、无状态，便于单测与历史回放复用。
"""

from __future__ import annotations

import math

from scipy.special import betainc, betaln

from core.evolution.types import GateParams, GateVerdict

# Wald 方向游走步长（theta_1=0.70 固定设计常量，不入配置）：
# 胜 +ln(2*theta_1)=ln1.4，负 ln(2*(1-theta_1))=ln0.6。
_WALD_WIN = math.log(1.4)
_WALD_LOSS = math.log(0.6)

# delta_shrunk 的伪计数（Agresti-Coull 风格收缩，只作观测输出不进判据）。
_SHRINK_PSEUDO = 4


def compute_e_value(w: int, l: int) -> float:  # noqa: E741
    """截断 Beta 混合 e 值：E = 2^(W+L+1)*B(W+1,L+1)*[1-I_1/2(W+1,L+1)]。

    参数:
        w: 基线错->候选对的翻转数。
        l: 基线对->候选错的翻转数。

    返回:
        e 值（W=L=0 时为 1）。

    异常:
        ValueError: 翻转计数为负时抛出。

    关键实现细节:
        log 空间计算在 n_max<=40 的设计工作区间内数值稳定（数百级计数
        亦可）；极大计数（>1000）时最终 exp 仍可能溢出。用正则化不完全
        Beta 的对称性 1-I_1/2(a,b) = I_1/2(b,a) 避免 1-x 的灾难性精度损失。
    """
    if w < 0 or l < 0:
        raise ValueError(f"翻转计数不能为负: w={w}, l={l}")
    a, b = w + 1, l + 1
    tail = betainc(b, a, 0.5)  # = 1 - I_1/2(a, b)
    if tail <= 0.0:
        return 0.0
    log_e = (w + l + 1) * math.log(2.0) + betaln(a, b) + math.log(tail)
    return math.exp(log_e)


def gate_decision(
    w: int,
    l: int,  # noqa: E741
    n_used: int,
    n_remaining: int,
    *,
    params: GateParams,
) -> GateVerdict:
    """块间四出口判定（每块结束时调用一次）。

    出口优先级：CONFIRMED（有证书先走）-> 方向拒绝 -> futility 拒绝 ->
    题尽（provisional / inertia）-> continue。

    参数:
        w: 累计 W。
        l: 累计 L。
        n_used: 已消费的阶梯题数（含一致题）。
        n_remaining: 阶梯剩余可用题数（min(阶梯长, n_max) - n_used）。
        params: 判据阈值组。

    返回:
        GateVerdict（decision + e 值/游走/效应量诊断）。

    异常:
        ValueError: n_used <= 0 或 n_remaining < 0 时抛出。
    """
    if n_used <= 0:
        raise ValueError(f"gate_decision 须在至少消费一块后调用: n_used={n_used}")
    if n_remaining < 0:
        raise ValueError(f"n_remaining 不能为负: {n_remaining}")
    e_value = compute_e_value(w, l)
    wald = w * _WALD_WIN + l * _WALD_LOSS
    delta_hat = (w - l) / n_used
    delta_shrunk = (w - l) / (n_used + _SHRINK_PSEUDO)

    if e_value >= params.e_confirm and delta_hat >= params.delta_min:
        decision = "accept_confirmed"
    elif wald <= params.lambda_dir:
        decision = "reject_directional"
    elif n_remaining > 0 and compute_e_value(w + n_remaining, l) < params.e_provisional:
        # futility 只在题未尽时有意义；题尽后的弱证据归 inertia 出口。
        decision = "reject_futility"
    elif n_remaining <= 0:
        if (
            e_value >= params.e_provisional
            and (w - l) >= params.w_net_min
            and delta_hat >= params.delta_min
        ):
            decision = "accept_provisional"
        else:
            decision = "reject_inertia"
    else:
        decision = "continue"
    return GateVerdict(decision, e_value, wald, delta_hat, delta_shrunk)


def probation_verdict(w: int, l: int, *, params: GateParams) -> str:  # noqa: E741
    """试用期一次性结算：固定样本 e 值双向检验。

    参数:
        w: 结算配对的 W（锚快照错->候选重跑对）。
        l: 结算配对的 L（锚快照对->候选重跑错）。
        params: 判据阈值组（用 e_confirm / e_rollback）。

    返回:
        "confirmed"（E>=e_confirm 转正）/ "rollback"（对称 E'>=e_rollback 回滚）
        / "unverified"（证据不足，elitist 惯性转正）。
    """
    if compute_e_value(w, l) >= params.e_confirm:
        return "confirmed"
    if compute_e_value(l, w) >= params.e_rollback:
        return "rollback"
    return "unverified"
