"""Confidence feature extraction from upstream pipeline stages.

Computes field-level features from extraction, resolution, evidence,
sentiment, and cross-stage agreement to produce a ConfidenceFeatures
vector for calibration or conservative defaults.
"""

from __future__ import annotations

import logging
from dataclasses import dataclass

from services.intelligence_pipeline_v3.confidence.models import ConfidenceFeatures

logger = logging.getLogger(__name__)


@dataclass
class ExtractionStageResult:
    """Subset of extraction results relevant to confidence features.

    This is an adapter interface — callers populate it from
    the full extraction/specialist output.
    """

    entity_scores: list[float]
    """Per-entity confidence scores from specialist extractor."""

    relation_scores: list[float]
    """Per-relation confidence scores."""

    total_facts: int
    """Total facts extracted."""

    valid_numeric_facts: int
    """Facts that passed deterministic parser validation."""

    populated_fields: int
    """Schema fields that have values."""

    expected_fields: int
    """Total expected schema fields for this document type."""


@dataclass
class ResolutionStageResult:
    """Subset of resolution results relevant to confidence features."""

    ambiguity_margins: list[float]
    """Per-mention ambiguity margins (gap between top-2 candidates)."""


@dataclass
class EvidenceStageResult:
    """Subset of evidence verification results relevant to confidence features."""

    total_claims: int
    """Total extracted claims/facts."""

    supported_claims: int
    """Claims backed by valid evidence spans."""


@dataclass
class SentimentStageResult:
    """Subset of sentiment results relevant to confidence features."""

    max_class_probabilities: list[float]
    """Per-company maximum class probability after calibration."""

    calibration_version: str
    """Version of sentiment calibration artifact used."""


@dataclass
class AgreementStageResult:
    """Cross-stage agreement analysis results."""

    agreement_ratio: float
    """Fraction of facts that agree across independent extraction paths."""

    novelty_certainty: float
    """Certainty of the novelty/duplicate classification (0-1)."""

    hard_case_score: float
    """Score indicating presence of known difficult patterns."""


class ConfidenceFeatureExtractor:
    """Extracts confidence features from upstream pipeline stage results.

    Produces a normalized ConfidenceFeatures vector that can be passed
    to the calibrator or used to determine conservative defaults.
    """

    def extract_features(
        self,
        extraction_result: ExtractionStageResult,
        resolution_result: ResolutionStageResult,
        evidence_result: EvidenceStageResult,
        sentiment_result: SentimentStageResult,
        agreement_result: AgreementStageResult | None = None,
        document_type: str = "unknown",
    ) -> ConfidenceFeatures:
        """Compute confidence features from all upstream stage results.

        Parameters
        ----------
        extraction_result
            Entity/relation/fact extraction outputs with scores.
        resolution_result
            Symbol resolution outputs with ambiguity margins.
        evidence_result
            Evidence verification outputs with coverage stats.
        sentiment_result
            Sentiment classification outputs with calibrated probabilities.
        agreement_result
            Optional cross-stage agreement analysis. Defaults used if None.
        document_type
            Document type string for type-specific calibration.

        Returns
        -------
        ConfidenceFeatures
            Normalized feature vector ready for calibration.
        """
        # Entity span score: average of entity scores, or 0 if none
        entity_span_score = (
            sum(extraction_result.entity_scores) / len(extraction_result.entity_scores)
            if extraction_result.entity_scores
            else 0.0
        )

        # Alias resolution margin: average of per-mention margins
        alias_resolution_margin = (
            sum(resolution_result.ambiguity_margins)
            / len(resolution_result.ambiguity_margins)
            if resolution_result.ambiguity_margins
            else 1.0  # No ambiguity if no mentions to resolve
        )

        # Numeric parser validity: fraction of valid numeric facts
        numeric_parser_validity = (
            extraction_result.valid_numeric_facts / extraction_result.total_facts
            if extraction_result.total_facts > 0
            else 1.0  # No numeric facts = no parser failures
        )

        # Evidence coverage: fraction of claims with valid evidence
        evidence_coverage = (
            evidence_result.supported_claims / evidence_result.total_claims
            if evidence_result.total_claims > 0
            else 0.0
        )

        # Relation score: average relation confidence
        relation_score = (
            sum(extraction_result.relation_scores)
            / len(extraction_result.relation_scores)
            if extraction_result.relation_scores
            else 0.0
        )

        # Sentiment calibration confidence: average max class probability
        sentiment_calibration_confidence = (
            sum(sentiment_result.max_class_probabilities)
            / len(sentiment_result.max_class_probabilities)
            if sentiment_result.max_class_probabilities
            else 0.5  # Neutral default when no sentiment data
        )

        # Document completeness: fraction of expected fields populated
        document_completeness = (
            extraction_result.populated_fields / extraction_result.expected_fields
            if extraction_result.expected_fields > 0
            else 0.0
        )

        # Cross-stage agreement features (use defaults if not provided)
        if agreement_result is not None:
            cross_stage_agreement = agreement_result.agreement_ratio
            duplicate_novelty_certainty = agreement_result.novelty_certainty
            known_hard_case_patterns = agreement_result.hard_case_score
        else:
            cross_stage_agreement = 0.5  # Neutral default
            duplicate_novelty_certainty = 0.5
            known_hard_case_patterns = 0.0

        # Validate document type
        valid_types = {
            "news",
            "filing",
            "transcript",
            "press_release",
            "macro_event",
            "unknown",
        }
        if document_type not in valid_types:
            logger.warning(
                "Unknown document_type '%s', defaulting to 'unknown'", document_type
            )
            document_type = "unknown"

        return ConfidenceFeatures(
            entity_span_score=_clamp(entity_span_score),
            alias_resolution_margin=_clamp(alias_resolution_margin),
            numeric_parser_validity=_clamp(numeric_parser_validity),
            evidence_coverage=_clamp(evidence_coverage),
            relation_score=_clamp(relation_score),
            sentiment_calibration_confidence=_clamp(sentiment_calibration_confidence),
            cross_stage_agreement=_clamp(cross_stage_agreement),
            duplicate_novelty_certainty=_clamp(duplicate_novelty_certainty),
            document_completeness=_clamp(document_completeness),
            document_type=document_type,
            known_hard_case_patterns=_clamp(known_hard_case_patterns),
        )


def _clamp(value: float, low: float = 0.0, high: float = 1.0) -> float:
    """Clamp value to [low, high]."""
    return max(low, min(high, value))
