"""
Regime detection via Hurst exponent (R/S method) and DFA.

Classifies market regime into: trending, reverting, stable, chaotic.
Hurst and DFA are internal — only name + confidence exposed.
"""

import math
from dataclasses import dataclass


@dataclass
class Regime:
    name: str        # trending, reverting, stable, chaotic
    confidence: str  # high, medium, low


def _linreg_slope(xs: list, ys: list) -> float:
    """Simple linear regression slope."""
    n = len(xs)
    if n < 2:
        return 0.0
    sum_x = sum(xs)
    sum_y = sum(ys)
    sum_xy = sum(x * y for x, y in zip(xs, ys))
    sum_xx = sum(x * x for x in xs)
    denom = n * sum_xx - sum_x ** 2
    if abs(denom) < 1e-15:
        return 0.0
    return (n * sum_xy - sum_x * sum_y) / denom


def _compute_hurst(prices: list[float], window: int = 200) -> float:
    """Hurst Exponent via R/S analysis on log returns.

    H > 0.5: trending (momentum works)
    H < 0.5: mean-reverting (contrarian works)
    H ≈ 0.5: random walk

    Uses returns, not raw prices — raw prices are non-stationary
    which biases H toward 1.0.
    """
    raw = prices[-window:] if len(prices) >= window else prices
    if len(raw) < 50:
        return 0.5

    # Convert to log returns (stationary series)
    series = []
    for i in range(1, len(raw)):
        if raw[i] > 0 and raw[i - 1] > 0:
            series.append(math.log(raw[i] / raw[i - 1]))
        else:
            series.append(0.0)

    n = len(series)
    if n < 30:
        return 0.5

    max_k = min(n // 2, 80)

    rs_values = []
    sizes = []

    for k in range(10, max_k):
        subseries_count = n // k
        if subseries_count < 1:
            continue
        rs_list = []

        for i in range(subseries_count):
            sub = series[i * k : (i + 1) * k]
            if len(sub) < 2:
                continue
            mean = sum(sub) / len(sub)
            devs = [x - mean for x in sub]

            # Cumulative deviations
            cumdev = []
            s = 0.0
            for d in devs:
                s += d
                cumdev.append(s)

            R = max(cumdev) - min(cumdev)
            variance = sum(d * d for d in devs) / len(devs)
            S = math.sqrt(variance)

            if S > 1e-15:
                rs_list.append(R / S)

        if rs_list:
            avg_rs = sum(rs_list) / len(rs_list)
            if avg_rs > 0:
                rs_values.append(math.log(avg_rs))
                sizes.append(math.log(k))

    if len(sizes) < 3:
        return 0.5

    slope = _linreg_slope(sizes, rs_values)
    return max(0.0, min(1.0, slope))


def _compute_dfa(prices: list[float], window: int = 200) -> float:
    """Detrended Fluctuation Analysis. Pure math, no numpy.

    More stable than Hurst for non-stationary financial data.
    """
    series = prices[-window:] if len(prices) >= window else prices
    if len(series) < 50:
        return 0.5

    # Convert to log returns
    returns = []
    for i in range(1, len(series)):
        if series[i - 1] > 0 and series[i] > 0:
            returns.append(math.log(series[i] / series[i - 1]))
        else:
            returns.append(0.0)

    if not returns:
        return 0.5

    mean_ret = sum(returns) / len(returns)
    profile = []
    cum = 0.0
    for r in returns:
        cum += r - mean_ret
        profile.append(cum)

    n_values = []
    f_values = []

    for box_n in range(10, len(profile) // 4):
        num_boxes = len(profile) // box_n
        fluctuations = []

        for i in range(num_boxes):
            segment = profile[i * box_n : (i + 1) * box_n]
            # Linear detrend
            xs = list(range(box_n))
            slope = _linreg_slope(xs, segment)
            intercept = sum(segment) / len(segment) - slope * sum(xs) / len(xs)
            trend = [slope * x + intercept for x in xs]
            residuals = [s - t for s, t in zip(segment, trend)]
            rms = math.sqrt(sum(r * r for r in residuals) / len(residuals))
            fluctuations.append(rms)

        if fluctuations:
            avg_f = sum(fluctuations) / len(fluctuations)
            if avg_f > 0:
                n_values.append(math.log(box_n))
                f_values.append(math.log(avg_f))

    if len(n_values) < 3:
        return 0.5

    slope = _linreg_slope(n_values, f_values)
    return max(0.0, min(2.0, slope))


class RegimeDetector:
    """Classify market regime from price closes.

    Thresholds:
      - trending:  hurst > 0.6
      - reverting:  hurst < 0.4
      - chaotic:   dfa < 0.4
      - stable:    default

    Confidence based on distance from boundary.
    """

    def classify(self, coin: str, closes: list[float]) -> Regime:
        hurst = _compute_hurst(closes)
        dfa = _compute_dfa(closes)

        # Chaotic: DFA very low regardless of hurst
        if dfa < 0.4:
            dist = 0.4 - dfa
            confidence = "high" if dist > 0.15 else ("medium" if dist > 0.05 else "low")
            return Regime(name="chaotic", confidence=confidence)

        # Trending: hurst > 0.6
        if hurst > 0.6:
            dist = hurst - 0.6
            confidence = "high" if dist > 0.15 else ("medium" if dist > 0.05 else "low")
            return Regime(name="trending", confidence=confidence)

        # Reverting: hurst < 0.4
        if hurst < 0.4:
            dist = 0.4 - hurst
            confidence = "high" if dist > 0.15 else ("medium" if dist > 0.05 else "low")
            return Regime(name="reverting", confidence=confidence)

        # Stable: default (hurst between 0.4 and 0.6, dfa >= 0.4)
        # Confidence based on how centered hurst is around 0.5
        dist_from_edge = min(hurst - 0.4, 0.6 - hurst)
        confidence = "high" if dist_from_edge > 0.08 else ("medium" if dist_from_edge > 0.03 else "low")
        return Regime(name="stable", confidence=confidence)
