"""Core types for LLM integration.

Provides enums, dataclasses, the ``LLMClient`` Protocol, and the
``LLMError`` exception used throughout the LLM integration package.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Protocol

from oscura.core.exceptions import OscuraError

# ==============================================================================
# Cost Constants (API-020: Cost Tracking)
# ==============================================================================

# Pricing per 1K tokens (approximate, as of 2024)
TOKEN_COSTS: dict[str, dict[str, float]] = {
    "gpt-4": {"input": 0.03, "output": 0.06},
    "gpt-4-turbo": {"input": 0.01, "output": 0.03},
    "gpt-4o": {"input": 0.005, "output": 0.015},
    "gpt-4o-mini": {"input": 0.00015, "output": 0.0006},
    "gpt-3.5-turbo": {"input": 0.0005, "output": 0.0015},
    "claude-3-opus": {"input": 0.015, "output": 0.075},
    "claude-3-opus-20240229": {"input": 0.015, "output": 0.075},
    "claude-3-sonnet": {"input": 0.003, "output": 0.015},
    "claude-3-sonnet-20240229": {"input": 0.003, "output": 0.015},
    "claude-3-haiku": {"input": 0.00025, "output": 0.00125},
    "claude-3-haiku-20240307": {"input": 0.00025, "output": 0.00125},
    "claude-3-5-sonnet": {"input": 0.003, "output": 0.015},
    "claude-3-5-sonnet-20241022": {"input": 0.003, "output": 0.015},
    "default": {"input": 0.001, "output": 0.002},
}


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

    OPENAI = "openai"
    ANTHROPIC = "anthropic"
    LOCAL = "local"
    CUSTOM = "custom"


class AnalysisHook(Enum):
    """Hook points for LLM integration."""

    BEFORE_ANALYSIS = "before_analysis"
    AFTER_ANALYSIS = "after_analysis"
    ON_ERROR = "on_error"


@dataclass
class LLMConfig:
    """Configuration for LLM integration.

    Attributes:
        provider: LLM provider to use
        model: Model identifier (e.g., 'gpt-4', 'claude-3-opus')
        api_key: API key for cloud providers (optional)
        base_url: Custom API endpoint (for local/custom providers)
        privacy_mode: If True, no data sent to cloud (local only)
        timeout: Request timeout in seconds
        max_retries: Maximum retry attempts for failed requests
        requests_per_minute: Rate limit for API requests (API-020)
        enable_cache: If True, cache responses for repeated queries (API-020)
        track_costs: If True, track token usage and costs (API-020)
    """

    provider: LLMProvider = LLMProvider.LOCAL
    model: str = "default"
    api_key: str | None = None
    base_url: str | None = None
    privacy_mode: bool = True
    timeout: float = 30.0
    max_retries: int = 3
    requests_per_minute: int = 60
    enable_cache: bool = False
    track_costs: bool = True


def estimate_tokens(text: str) -> int:
    """Estimate token count for text (API-019: token counting).

    Uses approximate character-to-token ratio. Actual count varies by model.

    Args:
        text: Input text to estimate tokens for

    Returns:
        Estimated token count (roughly 4 characters per token)
    """
    # Average ~4 chars per token for English text
    return max(1, len(text) // 4)


@dataclass
class LLMResponse:
    """Response from LLM query.

    Attributes:
        answer: Main text response
        confidence: Confidence score (0-1) if available
        suggested_commands: List of suggested Oscura commands
        metadata: Additional metadata from LLM
        raw_response: Raw response data for debugging
        estimated_cost: Estimated cost in USD for this request (API-020)
        cached: Whether this response was served from cache (API-020)
    """

    answer: str
    confidence: float | None = None
    suggested_commands: list[str] = field(default_factory=list)
    metadata: dict[str, Any] = field(default_factory=dict)
    raw_response: dict[str, Any] | None = None
    estimated_cost: float = 0.0
    cached: bool = False


class LLMClient(Protocol):
    """Protocol for LLM client implementations."""

    def query(self, prompt: str, context: dict[str, Any]) -> LLMResponse:
        """Send query to LLM.

        Args:
            prompt: User prompt
            context: Analysis context (trace metadata, etc.)
        """
        ...

    def analyze(self, trace: Any, question: str) -> LLMResponse:
        """Analyze trace with natural language question.

        Args:
            trace: Trace object
            question: Natural language question
        """
        ...

    def explain(self, measurement: Any) -> str:
        """Explain a measurement result.

        Args:
            measurement: Measurement result
        """
        ...


class LLMError(OscuraError):
    """LLM integration error."""
