"""Thin wrapper around the Anthropic Messages API via the `anthropic` SDK."""

from __future__ import annotations

import base64
import mimetypes
from pathlib import Path
import logging
from typing import Optional, Sequence, Union
from urllib.error import URLError
from urllib.request import urlopen

from anthropic import APIError, Anthropic

ImageInput = Union[str, Path]
logger = logging.getLogger(__name__)


class AnthropicClient:
    """Convenience wrapper around the Anthropic Messages API."""

    def generate_response(
        self,
        *,
        api_key: str,
        prompt: str,
        model: str,
        max_tokens: int = 32000,
        reasoning_effort: Optional[str] = None,
        images: Optional[Sequence[ImageInput]] = None,
    ) -> str:
        """Generate a response from the specified Anthropic model.

        Args:
            api_key: API key used to authenticate with Anthropic.
            prompt: Natural-language instruction or query for the model.
            model: Identifier of the Anthropic model to target (for example, ``"claude-3-5-sonnet-20241022"``).
            max_tokens: Cap for tokens across the entire exchange, defaults to 32000.
            reasoning_effort: Included for API parity; currently unused by the Anthropic SDK.
            images: Optional collection of image references (local paths, URLs, or data URLs).

        Returns:
            The text output produced by the model.

        Raises:
            ValueError: If required arguments are missing or the request payload is empty.
            URLError: If an image URL cannot be retrieved.
            APIError: If the underlying Anthropic request fails.
        """
        if not api_key:
            raise ValueError("api_key must be provided.")
        if not prompt and not images:
            raise ValueError("At least one of prompt or images must be provided.")
        if not model:
            raise ValueError("model must be provided.")

        content_blocks: list[dict] = []
        if prompt:
            content_blocks.append({"type": "text", "text": prompt})

        if images:
            for image in images:
                content_blocks.append(self._to_image_block(image))

        if not content_blocks:
            raise ValueError("No content provided for response generation.")

        client = Anthropic(api_key=api_key)

        try:
        response = client.messages.create(
            model=model,
            max_tokens=max_tokens,
            messages=[{"role": "user", "content": content_blocks}],
        )
        except Exception as exc:
            logger.exception("Anthropic messages.create failed: %s", exc)
            raise

        text_blocks: list[str] = []
        for block in getattr(response, "content", []) or []:
            if getattr(block, "type", None) == "text":
                text = getattr(block, "text", None)
                if text:
                    text_blocks.append(text)

        if text_blocks:
            result_text = "".join(text_blocks)
            logger.info(
                "Anthropic messages.create succeeded: model=%s images=%d text_len=%d",
                model,
                len(images or []),
                len(result_text or ""),
            )
            return result_text

        # Treat successful calls without textual content as a successful, empty response
        # rather than raising. This aligns with callers that handle empty outputs gracefully.
        logger.info(
            "Anthropic messages.create succeeded with no text: model=%s images=%d",
            model,
            len(images or []),
        )
        return ""

    def list_models(self, *, api_key: str) -> list[dict[str, Optional[str]]]:
        """Return the models available to the authenticated Anthropic account."""
        if not api_key:
            raise ValueError("api_key must be provided.")

        client = Anthropic(api_key=api_key)
        models: list[dict[str, Optional[str]]] = []

        try:
            iterator = client.models.list()
        except Exception as exc:
            logger.exception("Anthropic list models failed: %s", exc)
            raise

        for model in iterator:
            model_id = getattr(model, "id", None)
            if model_id is None and isinstance(model, dict):
                model_id = model.get("id")
            if not model_id:
                continue

            display_name = getattr(model, "display_name", None)
            if display_name is None and isinstance(model, dict):
                display_name = model.get("display_name")

            models.append({"id": model_id, "display_name": display_name})

        logger.info("Anthropic list_models succeeded: count=%d", len(models))
        return models

    @staticmethod
    def _to_image_block(image: ImageInput) -> dict:
        """Convert an image reference into an Anthropic content block."""
        if isinstance(image, Path):
            return _block_from_path(image)

        if image.startswith("data:"):
            return _block_from_data_url(image)

        if image.startswith(("http://", "https://")):
            return _block_from_url(image)

        return _block_from_path(Path(image))


def _block_from_path(path: Path) -> dict:
    """Create an image block from a local filesystem path."""
    expanded = path.expanduser()
    data = expanded.read_bytes()
    encoded = base64.b64encode(data).decode("utf-8")
    mime_type = mimetypes.guess_type(expanded.name)[0] or "application/octet-stream"

    return {
        "type": "image",
        "source": {
            "type": "base64",
            "media_type": mime_type,
            "data": encoded,
        },
    }


def _block_from_url(url: str) -> dict:
    """Create an image block by downloading content from a URL."""
    with urlopen(url) as response:
        data = response.read()
        mime_type = response.info().get_content_type()

    if not mime_type or mime_type == "application/octet-stream":
        mime_type = mimetypes.guess_type(url)[0] or "application/octet-stream"

    encoded = base64.b64encode(data).decode("utf-8")

    return {
        "type": "image",
        "source": {
            "type": "base64",
            "media_type": mime_type,
            "data": encoded,
        },
    }


def _block_from_data_url(data_url: str) -> dict:
    """Create an image block from a data URL."""
    header, encoded = data_url.split(",", 1)
    metadata = header[len("data:") :]
    mime_type = "application/octet-stream"
    is_base64 = False

    if ";" in metadata:
        mime_type_part, _, remainder = metadata.partition(";")
        if mime_type_part:
            mime_type = mime_type_part
        is_base64 = "base64" in remainder
    elif metadata:
        mime_type = metadata

    if is_base64:
        data_b64 = encoded
    else:
        data_bytes = encoded.encode("utf-8")
        data_b64 = base64.b64encode(data_bytes).decode("utf-8")

    return {
        "type": "image",
        "source": {
            "type": "base64",
            "media_type": mime_type or "application/octet-stream",
            "data": data_b64,
        },
    }
