"""SentimentScorer — abstracts sentiment model behind a protocol.

Supports both FinBERT production inference and deterministic test mode.
Returns per-text probability distributions and aggregates across evidence
texts for a company, integrating with the calibration pipeline.
"""

from __future__ import annotations

import time
from typing import Protocol, runtime_checkable

from services.intelligence_pipeline_v3.sentiment.aggregation import (
    aggregate_evidence_sentiments,
)
from services.intelligence_pipeline_v3.sentiment.calibrator import SentimentCalibrator
from services.intelligence_pipeline_v3.sentiment.finbert_adapter import FinBERTAdapter
from services.intelligence_pipeline_v3.sentiment.models import (
    CompanySentimentResult,
    EvidenceGroup,
    SentimentBatchResult,
    TextSentiment,
)


@runtime_checkable
class SentimentModel(Protocol):
    """Protocol for sentiment classification models.

    Any model implementing this interface can be used by SentimentScorer,
    enabling easy swapping between FinBERT, mock models, or future
    alternatives without changing the scoring logic.
    """

    @property
    def model_version(self) -> str:
        """Return the model version string for lineage tracking."""
        ...

    def classify(self, texts: list[str]) -> list[tuple[float, float, float]]:
        """Classify texts and return (positive, negative, neutral) tuples."""
        ...


class SentimentScorer:
    """Scores evidence groups for company-specific sentiment.

    Orchestrates the full sentiment pipeline:
    1. Classifies each evidence text via the underlying model
    2. Stores per-text probability distributions
    3. Optionally calibrates raw scores
    4. Aggregates and detects mixed sentiment from disagreement

    Parameters
    ----------
    model
        Sentiment classification model implementing the SentimentModel protocol.
        Defaults to FinBERTAdapter in test mode.
    calibrator
        Optional calibration wrapper for raw probabilities.
    """

    def __init__(
        self,
        model: SentimentModel | None = None,
        calibrator: SentimentCalibrator | None = None,
    ) -> None:
        self._model: SentimentModel = model or FinBERTAdapter(test_mode=True)
        self._calibrator = calibrator

    @property
    def model_version(self) -> str:
        """Return the underlying model version."""
        return self._model.model_version

    @property
    def calibration_version(self) -> str:
        """Return the calibration version (or 'uncalibrated')."""
        if self._calibrator and self._calibrator.is_fitted:
            return self._calibrator.calibration_version
        return "uncalibrated"

    async def score(self, evidence_group: EvidenceGroup) -> CompanySentimentResult:
        """Score a single evidence group and return aggregated company sentiment.

        Parameters
        ----------
        evidence_group
            Company-linked evidence group with texts to classify.

        Returns
        -------
        CompanySentimentResult
            Aggregated sentiment with per-text scores, mixed detection,
            and full probability distributions.
        """
        # Classify all texts in the group
        raw_probs = self._model.classify(evidence_group.texts)

        # Build per-text sentiment scores
        per_text_scores: list[TextSentiment] = []
        for i, (pos, neg, neu) in enumerate(raw_probs):
            evidence_id = evidence_group.evidence_ids[i]

            # Apply calibration if available
            if self._calibrator and self._calibrator.is_fitted:
                calibrated = self._calibrator.calibrate([pos, neg, neu])
                pos, neg, neu = calibrated[0], calibrated[1], calibrated[2]

            per_text_scores.append(
                TextSentiment(
                    evidence_id=evidence_id,
                    positive_prob=pos,
                    negative_prob=neg,
                    neutral_prob=neu,
                )
            )

        # Aggregate across evidence texts with mixed detection
        return aggregate_evidence_sentiments(
            company_id=evidence_group.company_id,
            per_text_scores=per_text_scores,
            model_version=self._model.model_version,
            calibration_version=self.calibration_version,
        )

    async def score_batch(
        self, evidence_groups: dict[str, EvidenceGroup]
    ) -> SentimentBatchResult:
        """Score multiple evidence groups and return batch results.

        Parameters
        ----------
        evidence_groups
            Mapping of company_id -> EvidenceGroup.

        Returns
        -------
        SentimentBatchResult
            Batch result with per-company sentiment and timing.
        """
        start_ms = int(time.time() * 1000)

        results: list[CompanySentimentResult] = []
        for _company_id, group in evidence_groups.items():
            result = await self.score(group)
            results.append(result)

        elapsed_ms = int(time.time() * 1000) - start_ms

        return SentimentBatchResult(
            results=results,
            model_version=self._model.model_version,
            processing_time_ms=elapsed_ms,
        )
