"""Anthropic adapter — instruments ``client.messages.create``.

Recognizes an ``anthropic.Anthropic`` / ``AsyncAnthropic`` client by module, and
reads the model, token usage, and output text out of each ``messages.create``
call/response. The ``anthropic`` package is NOT a hard dependency: the adapter
duck-types on the client, so it works with the real SDK or a stand-in.
"""
from __future__ import annotations

import functools
from typing import Any

from .base import AbomRun, ModelCall, _sha256, register

ANTHROPIC_ENDPOINT = "api.anthropic.com"


def _output_text(response: Any) -> str:
    """Best-effort extraction of the response text for hashing."""
    # SDK returns an object with .content = [ {type:"text", text:"..."}, ... ]
    content = getattr(response, "content", None)
    if content is None and isinstance(response, dict):
        content = response.get("content")
    if content is None:
        return ""
    parts = []
    for block in content:
        text = getattr(block, "text", None)
        if text is None and isinstance(block, dict):
            text = block.get("text")
        if text:
            parts.append(text)
    return "".join(parts)


def _tokens(response: Any) -> int | None:
    usage = getattr(response, "usage", None)
    if usage is None and isinstance(response, dict):
        usage = response.get("usage")
    if usage is None:
        return None
    inp = getattr(usage, "input_tokens", None)
    out = getattr(usage, "output_tokens", None)
    if inp is None and isinstance(usage, dict):
        inp, out = usage.get("input_tokens"), usage.get("output_tokens")
    if inp is None and out is None:
        return None
    return (inp or 0) + (out or 0)


def parse(model: str, response: Any) -> ModelCall:
    """Normalize an Anthropic response into a :class:`ModelCall`."""
    text = _output_text(response)
    return ModelCall(
        model=model,
        tokens=_tokens(response),
        output_sha256=_sha256(text) if text else None,
        endpoint=ANTHROPIC_ENDPOINT,
    )


class AnthropicAdapter:
    provider = "anthropic"

    def matches(self, client: Any) -> bool:
        mod = type(client).__module__ or ""
        name = type(client).__name__
        return mod.split(".")[0] == "anthropic" or name in ("Anthropic", "AsyncAnthropic")

    def wrap(self, client: Any, run: AbomRun) -> Any:
        messages = getattr(client, "messages", None)
        if messages is None or not hasattr(messages, "create"):
            raise TypeError(
                "Anthropic adapter expects a client with .messages.create; "
                f"got {type(client).__name__}"
            )
        original = messages.create

        @functools.wraps(original)
        def create(*args: Any, **kwargs: Any) -> Any:
            model = kwargs.get("model") or (args[0] if args else "unknown")
            # gate BEFORE the call leaves the boundary (deny-by-default)
            run.enforce(ModelCall(model=model, endpoint=ANTHROPIC_ENDPOINT))
            response = original(*args, **kwargs)
            run.record(parse(model, response))
            return response

        messages.create = create  # type: ignore[attr-defined]
        return client


register(AnthropicAdapter())
