from dataclasses import dataclass
import numpy as np
import scipy.stats as stats

from cpistats.core.data_validation import to_numpy_1d


@dataclass
class AndersonDarlingNormalityResult:
    statistic_raw: float
    statistic_adjusted: float
    p_value: float
    mean: float
    std_dev: float
    sample_size: int


def anderson_darling_normality(data):
    """
    Perform the Anderson–Darling Normality Test.

    Parameters
    ----------
    data : array-like
        Sample observations. Accepts list, NumPy array, or Pandas Series.

    Returns
    -------
    AndersonDarlingNormalityResult
        Object containing the Anderson–Darling statistic, adjusted statistic,
        p-value, sample mean, standard deviation, and sample size.
    """

    x = to_numpy_1d(data)

    n = len(x)
    x_sorted = np.sort(x)
    i = np.arange(1, n + 1)

    mu = np.mean(x)
    sigma = np.std(x, ddof=1)

    Fx = stats.norm.cdf(x_sorted, loc=mu, scale=sigma)
    Fx = np.clip(Fx, 1e-15, 1 - 1e-15)

    Di = (2 * i - 1) * np.log(Fx) + (2 * (n - i) + 1) * np.log(1 - Fx)
    A2_raw = -n - (1 / n) * np.sum(Di)

    A2_adj = A2_raw * (1 + 0.75 / n + 2.25 / n ** 2)

    if A2_adj >= 0.6:
        p_value = np.exp(1.2937 - 5.709 * A2_adj + 0.0186 * A2_adj ** 2)
    elif A2_adj >= 0.34:
        p_value = np.exp(0.9177 - 4.279 * A2_adj - 1.38 * A2_adj ** 2)
    elif A2_adj >= 0.2:
        p_value = 1 - np.exp(-8.318 + 42.796 * A2_adj - 59.938 * A2_adj ** 2)
    else:
        p_value = 1 - np.exp(-13.436 + 101.14 * A2_adj - 223.73 * A2_adj ** 2)

    return AndersonDarlingNormalityResult(
        statistic_raw=A2_raw,
        statistic_adjusted=A2_adj,
        p_value=p_value,
        mean=mu,
        std_dev=sigma,
        sample_size=n
    )