"""
LLM Provider Base Classes

Abstract base classes for LLM providers, enabling pluggable
backends (Claude, OpenAI, local models).
"""

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from enum import Enum
from typing import Any


class LLMProvider(str, Enum):
    """Supported LLM providers."""

    ANTHROPIC = "anthropic"
    OPENAI = "openai"


@dataclass
class LLMUsage:
    """Token usage information from LLM call."""

    prompt_tokens: int = 0
    completion_tokens: int = 0
    total_tokens: int = 0
    cached_tokens: int = 0

    @property
    def cost_estimate(self) -> float:
        """Rough cost estimate in USD (varies by model)."""
        # Conservative estimate based on Claude pricing
        input_cost = self.prompt_tokens * 0.000003
        output_cost = self.completion_tokens * 0.000015
        return input_cost + output_cost


@dataclass
class LLMResponse:
    """Response from an LLM provider."""

    content: str
    model: str
    provider: LLMProvider
    usage: LLMUsage = field(default_factory=LLMUsage)
    latency_ms: float = 0.0
    finish_reason: str = ""
    raw_response: Any | None = None

    @property
    def success(self) -> bool:
        """Check if response was successful."""
        return bool(self.content) and self.finish_reason in ("end_turn", "stop", "length")


class BaseLLMProvider(ABC):
    """
    Abstract base class for LLM providers.

    All LLM providers must implement this interface to ensure
    consistent behavior across different backends.

    Example:
        provider = ClaudeProvider(config)
        await provider.initialize()

        response = await provider.generate(
            prompt="What is nuclear criticality?",
            system="You are a nuclear safety expert.",
            temperature=0.7
        )
        print(response.content)
    """

    @abstractmethod
    async def initialize(self) -> None:
        """Initialize the provider (validate API key, etc.)."""
        pass

    @abstractmethod
    async def generate(
        self,
        prompt: str,
        system: str | None = None,
        temperature: float = 0.7,
        max_tokens: int = 4096,
        stop_sequences: list[str] | None = None,
        **kwargs,
    ) -> LLMResponse:
        """
        Generate a response from the LLM.

        Args:
            prompt: User prompt/query
            system: System prompt (instructions for the model)
            temperature: Sampling temperature (0.0 = deterministic, 1.0 = creative)
            max_tokens: Maximum tokens in response
            stop_sequences: Sequences that stop generation
            **kwargs: Provider-specific parameters

        Returns:
            LLMResponse with generated content and metadata
        """
        pass

    @abstractmethod
    async def generate_with_messages(
        self,
        messages: list[dict[str, str]],
        system: str | None = None,
        temperature: float = 0.7,
        max_tokens: int = 4096,
        **kwargs,
    ) -> LLMResponse:
        """
        Generate a response from a message history.

        Args:
            messages: List of {"role": "user"|"assistant", "content": "..."}
            system: System prompt
            temperature: Sampling temperature
            max_tokens: Maximum tokens
            **kwargs: Provider-specific parameters

        Returns:
            LLMResponse with generated content
        """
        pass

    @abstractmethod
    async def close(self) -> None:
        """Clean up resources."""
        pass

    @property
    @abstractmethod
    def provider_name(self) -> LLMProvider:
        """Get the provider identifier."""
        pass

    @property
    @abstractmethod
    def model_name(self) -> str:
        """Get the model name being used."""
        pass

    async def __aenter__(self) -> "BaseLLMProvider":
        """Async context manager entry."""
        await self.initialize()
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
        """Async context manager exit."""
        await self.close()


class LLMError(Exception):
    """Base exception for LLM errors."""

    pass


class LLMRateLimitError(LLMError):
    """Rate limit exceeded."""

    def __init__(self, message: str, retry_after: float | None = None) -> None:
        super().__init__(message)
        self.retry_after = retry_after


class LLMAuthenticationError(LLMError):
    """Authentication failed (invalid API key)."""

    pass


class LLMContextLengthError(LLMError):
    """Prompt exceeds model's context length."""

    pass


class LLMProviderError(LLMError):
    """Provider-specific error (API down, etc.)."""

    pass
