"""Mixed sentiment detection from evidence-group disagreement.

When evidence groups for the same company disagree (some positive,
some negative), the resulting label is "mixed" rather than an
unconstrained model output. This follows the design requirement that
mixed sentiment comes from conflicting supported evidence, not from
a fourth softmax label.

NOTE: This module provides the legacy compute_mixed_sentiment function
for backward compatibility. New code should prefer
aggregation.aggregate_evidence_sentiments which accepts TextSentiment
objects directly.
"""

from __future__ import annotations

from services.intelligence_pipeline_v3.sentiment.models import CompanySentimentResult, TextSentiment

# Disagreement threshold: both positive and negative probabilities
# must be >= this value across evidence groups to signal disagreement
DISAGREEMENT_THRESHOLD = 0.3


def compute_mixed_sentiment(
    company_id: str,
    group_results: list[tuple[float, float, float]],
    evidence_ids: list[str],
    model_version: str,
    calibration_version: str = "uncalibrated",
) -> CompanySentimentResult:
    """Compute company sentiment from multiple evidence group results.

    Applies disagreement detection: if evidence groups for the same
    company show both positive and negative signals above the threshold,
    the label is "mixed".

    Parameters
    ----------
    company_id
        Resolved company identifier.
    group_results
        List of (positive_prob, negative_prob, neutral_prob) tuples,
        one per evidence group for this company.
    evidence_ids
        All evidence span IDs contributing to this result.
    model_version
        Sentiment model version string.
    calibration_version
        Calibration artifact version.

    Returns
    -------
    CompanySentimentResult
        Aggregated sentiment result with disagreement-based mixed detection.
    """
    if not group_results:
        # No evidence — neutral by default
        return CompanySentimentResult(
            company_id=company_id,
            label="neutral",
            positive_prob=0.0,
            negative_prob=0.0,
            neutral_prob=1.0,
            evidence_ids=evidence_ids,
            is_mixed=False,
            per_text_scores=[],
            model_version=model_version,
            calibration_version=calibration_version,
        )

    # Check for disagreement across groups
    is_mixed = _detect_disagreement(group_results)

    # Compute weighted average of group probabilities
    n = len(group_results)
    avg_pos = sum(r[0] for r in group_results) / n
    avg_neg = sum(r[1] for r in group_results) / n
    avg_neu = sum(r[2] for r in group_results) / n

    # Normalize to ensure probabilities sum to 1.0
    total = avg_pos + avg_neg + avg_neu
    if total > 0:
        avg_pos /= total
        avg_neg /= total
        avg_neu /= total
    else:
        avg_pos = 0.0
        avg_neg = 0.0
        avg_neu = 1.0

    # Determine label
    if is_mixed:
        label = "mixed"
    else:
        label = _argmax_label(avg_pos, avg_neg, avg_neu)

    # Build per-text scores from tuples for provenance
    per_text_scores: list[TextSentiment] = []
    for i, (pos, neg, neu) in enumerate(group_results):
        eid = evidence_ids[i] if i < len(evidence_ids) else f"unknown_{i}"
        per_text_scores.append(
            TextSentiment(
                evidence_id=eid,
                positive_prob=pos,
                negative_prob=neg,
                neutral_prob=neu,
            )
        )

    return CompanySentimentResult(
        company_id=company_id,
        label=label,
        positive_prob=round(avg_pos, 6),
        negative_prob=round(avg_neg, 6),
        neutral_prob=round(avg_neu, 6),
        evidence_ids=evidence_ids,
        is_mixed=is_mixed,
        per_text_scores=per_text_scores,
        model_version=model_version,
        calibration_version=calibration_version,
    )


def _detect_disagreement(group_results: list[tuple[float, float, float]]) -> bool:
    """Detect if evidence groups disagree on sentiment direction.

    Disagreement is detected when across all groups, the maximum
    positive probability is >= threshold AND the maximum negative
    probability is >= threshold. This means some evidence strongly
    suggests positive while other evidence strongly suggests negative.
    """
    if len(group_results) < 2:
        return False

    max_pos = max(r[0] for r in group_results)
    max_neg = max(r[1] for r in group_results)

    return max_pos >= DISAGREEMENT_THRESHOLD and max_neg >= DISAGREEMENT_THRESHOLD


def _argmax_label(pos: float, neg: float, neu: float) -> str:
    """Return the label with the highest probability."""
    if pos >= neg and pos >= neu:
        return "positive"
    elif neg >= pos and neg >= neu:
        return "negative"
    else:
        return "neutral"
