"""Anthropic provider implementation for LLM integration.

Provides ``AnthropicClient`` with chat completion, trace analysis,
measurement suggestion, and retry/rate-limiting support.
"""

from __future__ import annotations

import json
import os
import time
from typing import Any

from oscura.api.integrations.llm.cost import RateLimiter, _global_cost_tracker
from oscura.api.integrations.llm.types import LLMConfig, LLMError, LLMResponse

# ==============================================================================
# Anthropic message helpers
# ==============================================================================


def _convert_anthropic_messages(
    messages: list[dict[str, str]], system: str | None
) -> tuple[list[dict[str, str]], str | None]:
    """Convert messages to Anthropic format (separate system from user messages).

    Args:
        messages: Original messages with mixed roles.
        system: Optional system prompt.

    Returns:
        Tuple of (user_messages, system_message).
    """
    user_messages: list[dict[str, str]] = []
    system_message = system

    for msg in messages:
        if msg["role"] == "system" and not system_message:
            system_message = msg["content"]
        elif msg["role"] in ["user", "assistant"]:
            user_messages.append(msg)

    return (user_messages, system_message)


def _extract_anthropic_answer(response: Any) -> str:
    """Extract text answer from Anthropic response.

    Args:
        response: Anthropic API response.

    Returns:
        Concatenated text from all content blocks.
    """
    answer = ""
    for block in response.content:
        if hasattr(block, "text"):
            answer += block.text
    return answer


def _build_anthropic_response(response: Any, answer: str, estimated_cost: float) -> LLMResponse:
    """Build LLMResponse from Anthropic API response.

    Args:
        response: Anthropic API response.
        answer: Extracted answer text.
        estimated_cost: Estimated API cost.

    Returns:
        LLMResponse with metadata.
    """
    return LLMResponse(
        answer=answer,
        confidence=None,  # Anthropic doesn't provide confidence scores
        suggested_commands=[],
        metadata={
            "model": response.model,
            "usage": {
                "input_tokens": response.usage.input_tokens,
                "output_tokens": response.usage.output_tokens,
            },
            "stop_reason": response.stop_reason,
        },
        raw_response={
            "id": response.id,
            "type": response.type,
        },
        estimated_cost=estimated_cost,
    )


# ==============================================================================
# AnthropicClient
# ==============================================================================


class AnthropicClient:
    """Anthropic client implementation.

    Full implementation:
    - chat_completion() with retry logic
    - analyze_trace() for trace analysis
    - suggest_measurements() for measurement recommendations
    - API key from ANTHROPIC_API_KEY environment variable
    """

    def __init__(self, config: LLMConfig) -> None:
        """Initialize Anthropic client.

        Args:
            config: LLM configuration

        Raises:
            LLMError: If anthropic package not available
        """
        self.config = config
        self.rate_limiter = RateLimiter(config.requests_per_minute)

        # Import and initialize Anthropic client
        try:
            import anthropic  # type: ignore[import-not-found]

            self._anthropic = anthropic
        except ImportError:
            raise LLMError("Anthropic package not installed. Install with: pip install anthropic")

        # Get API key from config or environment
        api_key = config.api_key or os.environ.get("ANTHROPIC_API_KEY")
        if not api_key:
            raise LLMError(
                "Anthropic API key required. Set ANTHROPIC_API_KEY environment variable "
                "or pass api_key to configure()"
            )

        # Initialize Anthropic client
        self.client = self._anthropic.Anthropic(api_key=api_key, timeout=config.timeout)

    def chat_completion(
        self, messages: list[dict[str, str]], system: str | None = None, **kwargs: Any
    ) -> LLMResponse:
        """Send chat completion request with retry logic.

        Args:
            messages: List of message dicts with 'role' and 'content'
            system: System prompt (optional)
            **kwargs: Additional parameters for Anthropic API

        Returns:
            LLM response with answer and metadata

        Raises:
            LLMError: If API request fails after retries
        """
        self.rate_limiter.acquire()

        # Convert messages format for Anthropic
        user_messages, system_message = _convert_anthropic_messages(messages, system)

        # Retry loop with exponential backoff
        last_exception = None
        for attempt in range(self.config.max_retries):
            try:
                response = self._send_anthropic_request(user_messages, system_message, kwargs)
                answer = _extract_anthropic_answer(response)
                estimated_cost = self._track_anthropic_costs(response)
                return _build_anthropic_response(response, answer, estimated_cost)

            except self._anthropic.RateLimitError as e:
                last_exception = e
                if not self._handle_rate_limit_retry(attempt):
                    raise LLMError(f"Anthropic rate limit exceeded: {e}")

            except self._anthropic.APITimeoutError as e:
                last_exception = e
                if not self._handle_timeout_retry(attempt):
                    raise LLMError(f"Anthropic request timeout: {e}")

            except self._anthropic.APIError as e:
                last_exception = e
                if not self._handle_api_error_retry(attempt):
                    raise LLMError(f"Anthropic API error: {e}")

            except Exception as e:
                last_exception = e
                raise LLMError(f"Anthropic request failed: {e}")

        raise LLMError(
            f"Anthropic request failed after {self.config.max_retries} retries: {last_exception}"
        )

    def _send_anthropic_request(
        self,
        user_messages: list[dict[str, str]],
        system_message: str | None,
        kwargs: dict[str, Any],
    ) -> Any:
        """Send request to Anthropic API.

        Args:
            user_messages: Filtered user/assistant messages.
            system_message: System prompt.
            kwargs: Additional API parameters.

        Returns:
            Anthropic API response.
        """
        request_params: dict[str, Any] = {
            "model": self.config.model,
            "messages": user_messages,
            "max_tokens": kwargs.get("max_tokens", 1024),
        }
        if system_message:
            request_params["system"] = system_message

        for key in ["temperature", "top_p", "top_k"]:
            if key in kwargs:
                request_params[key] = kwargs[key]

        return self.client.messages.create(**request_params)

    def _track_anthropic_costs(self, response: Any) -> float:
        """Track token usage and return estimated cost.

        Args:
            response: Anthropic API response.

        Returns:
            Estimated cost in USD.
        """
        input_tokens = response.usage.input_tokens
        output_tokens = response.usage.output_tokens

        if self.config.track_costs:
            return _global_cost_tracker.record(response.model, input_tokens, output_tokens)
        return 0.0

    def _handle_rate_limit_retry(self, attempt: int) -> bool:
        """Handle rate limit error with exponential backoff.

        Args:
            attempt: Current attempt number.

        Returns:
            True if should continue retry, False if should raise.
        """
        if attempt < self.config.max_retries - 1:
            wait_time = 2**attempt
            time.sleep(wait_time)
            return True
        return False

    def _handle_timeout_retry(self, attempt: int) -> bool:
        """Handle timeout error with retry.

        Args:
            attempt: Current attempt number.

        Returns:
            True if should continue retry, False if should raise.
        """
        if attempt < self.config.max_retries - 1:
            time.sleep(1)
            return True
        return False

    def _handle_api_error_retry(self, attempt: int) -> bool:
        """Handle API error with retry.

        Args:
            attempt: Current attempt number.

        Returns:
            True if should continue retry, False if should raise.
        """
        if attempt < self.config.max_retries - 1:
            time.sleep(1)
            return True
        return False

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

        Args:
            trace: Trace object
            question: Natural language question about the trace

        Returns:
            LLM response with analysis
        """
        trace_summary = self._summarize_trace(trace)

        system_prompt = (
            "You are an expert in signal analysis and oscilloscope data. "
            "Analyze the provided trace data and answer questions accurately. "
            "Provide specific, actionable insights."
        )

        messages = [
            {
                "role": "user",
                "content": f"Trace Summary:\n{trace_summary}\n\nQuestion: {question}",
            },
        ]

        return self.chat_completion(messages, system=system_prompt)

    def suggest_measurements(self, trace: Any) -> LLMResponse:
        """Suggest measurements based on trace characteristics.

        Args:
            trace: Trace object

        Returns:
            LLM response with measurement suggestions
        """
        trace_summary = self._summarize_trace(trace)

        system_prompt = (
            "You are an expert in signal analysis. Based on trace characteristics, "
            "suggest relevant measurements. Provide 3-5 specific measurement "
            "recommendations with brief explanations."
        )

        messages = [
            {
                "role": "user",
                "content": (
                    f"Trace Summary:\n{trace_summary}\n\n"
                    "What measurements would be most informative for this trace?"
                ),
            },
        ]

        response = self.chat_completion(messages, system=system_prompt)

        suggested_commands = self._extract_commands(response.answer)
        response.suggested_commands = suggested_commands

        return response

    def _summarize_trace(self, trace: Any) -> str:
        """Create a text summary of trace for LLM context.

        Args:
            trace: Trace object

        Returns:
            Text summary of trace characteristics
        """
        summary_parts = [f"Trace Type: {type(trace).__name__}"]

        if hasattr(trace, "metadata"):
            meta = trace.metadata
            if hasattr(meta, "sample_rate"):
                summary_parts.append(f"Sample Rate: {meta.sample_rate:.2e} Hz")
            if hasattr(meta, "num_samples"):
                summary_parts.append(f"Number of Samples: {meta.num_samples:,}")
            if hasattr(meta, "duration"):
                summary_parts.append(f"Duration: {meta.duration:.6f} s")

        if hasattr(trace, "data"):
            import numpy as np

            data = trace.data
            summary_parts.extend(
                [
                    f"Mean: {np.mean(data):.6e}",
                    f"Std Dev: {np.std(data):.6e}",
                    f"Min: {np.min(data):.6e}",
                    f"Max: {np.max(data):.6e}",
                    f"Peak-to-Peak: {np.ptp(data):.6e}",
                ]
            )

        return "\n".join(summary_parts)

    def _extract_commands(self, text: str) -> list[str]:
        """Extract suggested Oscura commands from LLM response.

        Args:
            text: LLM response text

        Returns:
            List of extracted command strings
        """
        commands: list[str] = []
        measurement_keywords = [
            "rise_time",
            "fall_time",
            "frequency",
            "period",
            "amplitude",
            "rms",
            "thd",
            "snr",
            "fft",
            "psd",
            "peak",
            "duty_cycle",
        ]

        text_lower = text.lower()
        for keyword in measurement_keywords:
            if keyword in text_lower:
                commands.append(f"measure {keyword}")

        return commands

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

        Args:
            prompt: User prompt
            context: Analysis context

        Returns:
            LLM response
        """
        context_str = json.dumps(context, indent=2)
        system_prompt = "You are a helpful assistant for signal analysis."
        messages = [
            {
                "role": "user",
                "content": f"Context:\n{context_str}\n\nQuery: {prompt}",
            },
        ]
        return self.chat_completion(messages, system=system_prompt)

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

        Args:
            trace: Trace object
            question: Natural language question

        Returns:
            Analysis response
        """
        return self.analyze_trace(trace, question)

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

        Args:
            measurement: Measurement result

        Returns:
            Explanation text
        """
        system_prompt = (
            "You are an expert in signal measurement interpretation. "
            "Explain measurement results clearly and concisely."
        )
        messages = [
            {
                "role": "user",
                "content": f"Explain this measurement result: {measurement}",
            },
        ]
        response = self.chat_completion(messages, system=system_prompt)
        return response.answer
