"""
Attribution data models for releaseops.

Defines immutable structures for attribution results — explaining
why agents made specific decisions.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional


@dataclass(frozen=True)
class Influence:
    """One factor that influenced agent behavior."""

    artifact_type: str  # "prompt" | "policy" | "model_config"
    artifact_ref: str   # e.g., "prompts/system@1.0.1"
    confidence: float   # 0.0-1.0
    reasoning: str
    location: Optional[str] = None    # e.g., "line 23", "rule 5"
    content_snippet: str = ""         # max ~200 chars
    keywords_matched: Optional[List[str]] = None
    is_negated: bool = False          # True when matched line prohibits the action
    confidence_reason: str = ""       # Why this confidence level was assigned

    def __post_init__(self) -> None:
        if not 0.0 <= self.confidence <= 1.0:
            raise ValueError(f"confidence must be 0.0-1.0, got {self.confidence}")

    @property
    def confidence_label(self) -> str:
        if self.confidence >= 0.8:
            return "HIGH"
        elif self.confidence >= 0.5:
            return "MEDIUM"
        return "LOW"

    def to_dict(self) -> dict:
        d = {
            "artifact_type": self.artifact_type,
            "artifact_ref": self.artifact_ref,
            "confidence": self.confidence,
            "confidence_label": self.confidence_label,
            "reasoning": self.reasoning,
            "content_snippet": self.content_snippet,
        }
        if self.location:
            d["location"] = self.location
        if self.keywords_matched:
            d["keywords_matched"] = list(self.keywords_matched)
        if self.is_negated:
            d["is_negated"] = True
        if self.confidence_reason:
            d["confidence_reason"] = self.confidence_reason
        return d

    @classmethod
    def from_dict(cls, data: dict) -> Influence:
        return cls(
            artifact_type=data["artifact_type"],
            artifact_ref=data["artifact_ref"],
            confidence=data["confidence"],
            reasoning=data["reasoning"],
            location=data.get("location"),
            content_snippet=data.get("content_snippet", ""),
            keywords_matched=data.get("keywords_matched"),
            is_negated=data.get("is_negated", False),
            confidence_reason=data.get("confidence_reason", ""),
        )


@dataclass(frozen=True)
class Attribution:
    """Complete attribution result for a behavior."""

    trace_id: str
    bundle_id: str
    bundle_version: str
    action: str
    action_type: str  # "tool_call" | "response" | "decision"
    influences: List[Influence] = field(default_factory=list)
    overall_assessment: str = "Unclear"  # "Expected per artifacts" | "Unexpected" | "Unclear"
    analyzed_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())

    @property
    def primary_influence(self) -> Optional[Influence]:
        """Highest confidence influence, or None if empty."""
        if not self.influences:
            return None
        return self.influences[0]

    def get_influences_by_type(self, artifact_type: str) -> List[Influence]:
        """Filter influences by artifact type, ordered by confidence."""
        return sorted(
            [i for i in self.influences if i.artifact_type == artifact_type],
            key=lambda i: i.confidence,
            reverse=True,
        )

    def get_high_confidence_influences(self) -> List[Influence]:
        """Return only influences with confidence >= 0.8."""
        return [i for i in self.influences if i.confidence >= 0.8]

    def to_dict(self) -> dict:
        return {
            "trace_id": self.trace_id,
            "bundle_id": self.bundle_id,
            "bundle_version": self.bundle_version,
            "action": self.action,
            "action_type": self.action_type,
            "influences": [i.to_dict() for i in self.influences],
            "primary_influence": self.primary_influence.to_dict() if self.primary_influence else None,
            "overall_assessment": self.overall_assessment,
            "analyzed_at": self.analyzed_at,
        }

    @classmethod
    def from_dict(cls, data: dict) -> Attribution:
        influences = [Influence.from_dict(i) for i in data.get("influences", [])]
        return cls(
            trace_id=data["trace_id"],
            bundle_id=data["bundle_id"],
            bundle_version=data["bundle_version"],
            action=data["action"],
            action_type=data["action_type"],
            influences=influences,
            overall_assessment=data.get("overall_assessment", "Unclear"),
            analyzed_at=data.get("analyzed_at", ""),
        )
