"""Bayesian accumulator for probabilistic sentiment aggregation.

Accumulates weighted signals into a Bayesian posterior using
log-likelihood accumulation, Beta distribution parameters, and
Shannon entropy for mixed-signal detection.

Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 9.1, 9.7
"""
from __future__ import annotations

import math
from dataclasses import dataclass

from services.aggregation.scoring import WeightedSignal


@dataclass(frozen=True)
class BayesianPosterior:
    """Bayesian posterior state from signal accumulation."""

    p_bull: float  # σ(L_t), bullish probability [0, 1]
    alpha: float  # Beta distribution α parameter (≥ 1.0)
    beta: float  # Beta distribution β parameter (≥ 1.0)
    log_likelihood: float  # Raw log-likelihood accumulation L_t
    bayesian_confidence: float  # 1 - 4αβ/(α+β)², [0, 1]
    entropy: float  # Shannon entropy H, [0, 1]
    signal_count: int  # Number of signals processed


# Uninformative prior (no evidence)
PRIOR = BayesianPosterior(
    p_bull=0.5,
    alpha=1.0,
    beta=1.0,
    log_likelihood=0.0,
    bayesian_confidence=0.0,
    entropy=1.0,
    signal_count=0,
)


def compute_entropy(p_bull: float) -> float:
    """Shannon entropy H = -p·log₂(p) - (1-p)·log₂(1-p).

    Returns value in [0, 1]. Maximum at p=0.5, zero at p=0 or p=1.
    Handles edge cases p≤0 and p≥1 by returning 0.0.
    """
    if p_bull <= 0.0 or p_bull >= 1.0:
        return 0.0
    q = 1.0 - p_bull
    return -(p_bull * math.log2(p_bull) + q * math.log2(q))


def compute_bayesian_posterior(
    signals: list[WeightedSignal],
) -> BayesianPosterior:
    """Accumulate weighted signals into a Bayesian posterior.

    Computes:
    - Log-likelihood: L_t = Σ(w_i · s_i)
    - Bullish probability: P_bull = σ(L_t)
    - Beta posterior: α = 1 + W_bull, β = 1 + W_bear
    - Bayesian confidence: C = 1 - 4αβ/(α+β)²
    - Shannon entropy: H = -p·log₂(p) - (1-p)·log₂(1-p)

    Returns PRIOR for empty signal lists.
    Skips signals with NaN weight or sentiment.
    """
    if not signals:
        return PRIOR

    log_likelihood = 0.0
    w_bull = 0.0
    w_bear = 0.0
    count = 0

    for sig in signals:
        combined = sig.weight.combined
        sentiment = sig.sentiment_value

        # Skip signals with NaN weight or sentiment
        if math.isnan(combined) or math.isnan(sentiment):
            continue

        log_likelihood += combined * sentiment

        if sentiment > 0.0:
            w_bull += combined
        elif sentiment < 0.0:
            w_bear += combined

        count += 1

    if count == 0:
        return PRIOR

    # P_bull via sigmoid: σ(L_t) = 1 / (1 + exp(-L_t))
    # Guard against overflow in exp for very large |L_t|
    if log_likelihood > 500.0:
        p_bull = 1.0
    elif log_likelihood < -500.0:
        p_bull = 0.0
    else:
        p_bull = 1.0 / (1.0 + math.exp(-log_likelihood))

    # Beta posterior parameters
    alpha = 1.0 + w_bull
    beta_param = 1.0 + w_bear

    # Bayesian confidence: C = 1 - 4αβ/(α+β)²
    ab_sum = alpha + beta_param
    bayesian_confidence = 1.0 - (4.0 * alpha * beta_param) / (ab_sum * ab_sum)
    # Clamp to [0, 1] to guard against floating-point rounding
    bayesian_confidence = max(0.0, min(1.0, bayesian_confidence))

    # Shannon entropy
    entropy = compute_entropy(p_bull)

    return BayesianPosterior(
        p_bull=p_bull,
        alpha=alpha,
        beta=beta_param,
        log_likelihood=log_likelihood,
        bayesian_confidence=bayesian_confidence,
        entropy=entropy,
        signal_count=count,
    )
