"""Claude conductor adapter for remote API inference.

This adapter connects to Anthropic's Claude API. Partners must configure
their API key in environment or pass it directly.

Example usage:

    from partner_inference_harness.conductor import ClaudeConductor, ConductorRequest

    conductor = ClaudeConductor(api_key="sk-...")
    # or use environment: export ANTHROPIC_API_KEY=sk-...

    response = conductor.complete(ConductorRequest(
        query="What is machine learning?",
        max_tokens=256,
    ))
    print(response.text)
"""

from __future__ import annotations

import logging
import os
import time
from typing import Any

from .interface import (
    ConductorError,
    ConductorInterface,
    ConductorRequest,
    ConductorResponse,
)

logger = logging.getLogger(__name__)


class ClaudeConductor(ConductorInterface):
    """Conductor backed by Anthropic Claude API.

    Connects to Anthropic's API for remote model inference. Supports
    Claude Sonnet, Opus, and Haiku model families.

    Attributes:
        model: The Claude model identifier to use.
    """

    DEFAULT_MODEL = "claude-sonnet-4-20250514"
    DEFAULT_MAX_TOKENS = 4096

    def __init__(
        self,
        api_key: str | None = None,
        model: str = DEFAULT_MODEL,
        base_url: str | None = None,
        timeout_seconds: float = 60.0,
    ) -> None:
        """Initialize Claude conductor.

        Args:
            api_key: Anthropic API key. Falls back to ANTHROPIC_API_KEY env var.
            model: Claude model identifier.
            base_url: Optional base URL override (for proxies).
            timeout_seconds: Request timeout in seconds.

        Raises:
            ConductorError: If no API key is provided or found.
        """
        self.api_key = api_key or os.environ.get("ANTHROPIC_API_KEY")
        if not self.api_key:
            raise ConductorError(
                message="No Anthropic API key provided. Set ANTHROPIC_API_KEY "
                "environment variable or pass api_key parameter.",
                backend="claude",
                retriable=False,
            )

        self.model = model
        self.base_url = base_url
        self.timeout = timeout_seconds
        self._client: Any = None

    def _ensure_client(self) -> Any:
        """Lazily initialize the Anthropic client.

        Returns:
            Anthropic client instance.

        Raises:
            ConductorError: If anthropic package is not installed.
        """
        if self._client is None:
            try:
                import anthropic
            except ImportError as e:
                raise ConductorError(
                    message="anthropic package not installed. "
                    "Install with: pip install anthropic",
                    backend="claude",
                    retriable=False,
                ) from e

            client_kwargs: dict[str, Any] = {
                "api_key": self.api_key,
                "timeout": self.timeout,
            }
            if self.base_url:
                client_kwargs["base_url"] = self.base_url

            self._client = anthropic.Anthropic(**client_kwargs)

        return self._client

    @property
    def backend_type(self) -> str:
        """Return 'remote' indicating remote API backend."""
        return "remote"

    def complete(self, request: ConductorRequest) -> ConductorResponse:
        """Execute a completion request through Claude API.

        Args:
            request: The completion request containing query and parameters.

        Returns:
            ConductorResponse with generated text and metadata.

        Raises:
            ConductorError: If the completion request fails.
        """
        client = self._ensure_client()
        start_time = time.perf_counter()

        messages = self._build_messages(request)
        kwargs = self._build_kwargs(request, messages)

        try:
            response = client.messages.create(**kwargs)
        except Exception as e:
            error_type = type(e).__name__
            # Check if it's a rate limit or server error (retriable)
            retriable = "rate" in str(e).lower() or "503" in str(e) or "502" in str(e)
            raise ConductorError(
                message=f"Claude API error ({error_type}): {e}",
                backend="claude",
                retriable=retriable,
            ) from e

        elapsed_ms = (time.perf_counter() - start_time) * 1000

        return self._parse_response(response, elapsed_ms)

    def health_check(self) -> bool:
        """Check if Claude API is reachable.

        Performs a minimal request to verify API connectivity.

        Returns:
            True if API responds successfully.
        """
        try:
            client = self._ensure_client()
            # Minimal request to verify connectivity
            client.messages.create(
                model=self.model,
                max_tokens=1,
                messages=[{"role": "user", "content": "hi"}],
            )
            return True
        except Exception:
            return False

    def _build_messages(
        self,
        request: ConductorRequest,
    ) -> list[dict[str, str]]:
        """Build messages array for Claude API.

        Args:
            request: The conductor request.

        Returns:
            List of message dictionaries.
        """
        messages: list[dict[str, str]] = []

        messages.append({
            "role": "user",
            "content": request.query,
        })

        return messages

    def _build_kwargs(
        self,
        request: ConductorRequest,
        messages: list[dict[str, str]],
    ) -> dict[str, Any]:
        """Build keyword arguments for the API call.

        Args:
            request: The conductor request.
            messages: The messages array.

        Returns:
            Keyword arguments dictionary.
        """
        kwargs: dict[str, Any] = {
            "model": self.model,
            "messages": messages,
            "max_tokens": request.max_tokens,
        }

        # Add system prompt if context provided
        if request.context:
            kwargs["system"] = request.context

        # Only add temperature if not default (Claude has specific defaults)
        if request.temperature != 0.7:
            kwargs["temperature"] = request.temperature

        return kwargs

    def _parse_response(
        self,
        response: Any,
        elapsed_ms: float,
    ) -> ConductorResponse:
        """Parse Claude API response into ConductorResponse.

        Args:
            response: Raw API response object.
            elapsed_ms: Request latency in milliseconds.

        Returns:
            Parsed ConductorResponse.
        """
        # Extract text from content blocks
        text_parts = []
        for block in response.content:
            if hasattr(block, "text"):
                text_parts.append(block.text)

        text = "".join(text_parts)

        metadata = {
            "finish_reason": response.stop_reason,
            "prompt_tokens": response.usage.input_tokens,
            "completion_tokens": response.usage.output_tokens,
            "total_tokens": response.usage.input_tokens + response.usage.output_tokens,
            "model": response.model,
            "message_id": response.id,
        }

        return ConductorResponse(
            text=text,
            model_used=f"claude:{self.model}",
            latency_ms=elapsed_ms,
            metadata=metadata,
        )

    def close(self) -> None:
        """Close the client (no-op for Anthropic client)."""
        self._client = None

    def __enter__(self) -> "ClaudeConductor":
        """Context manager entry."""
        return self

    def __exit__(self, *args: Any) -> None:
        """Context manager exit."""
        self.close()
