"""Tool registry API interactions."""

from __future__ import annotations

import asyncio
from dataclasses import dataclass
from typing import Any, Iterable, cast, TypedDict
from urllib.parse import urljoin

import requests

from ..logging import get_logger

logger = get_logger(__name__)
REQUEST_TIMEOUT_SECONDS = 10


@dataclass
class RegisterToolProperties:
    """Tool metadata sent to the tool-registry API."""

    name: str
    description: str
    input_json_schema: dict
    output_json_schema: dict | None
    response_format: str

    def to_api_payload(self) -> dict[str, Any]:
        return {
            "name": self.name,
            "description": self.description,
            "inputJsonSchema": self.input_json_schema,
            "outputJsonSchema": self.output_json_schema,
            "responseFormat": self.response_format,
        }


class ToolRegistryItem(TypedDict, total=False):
    id: str
    name: str
    versionId: str


def _parse_register_tools_response(response_json: Any) -> dict[str, ToolRegistryItem]:
    """Convert the register-tools response into a tool-name to tool object map."""

    if not isinstance(response_json, list):
        raise ValueError("register_tools response must be a list")

    tool_name_to_tool: dict[str, ToolRegistryItem] = {}
    for idx, item in enumerate(response_json):
        if not isinstance(item, dict):
            raise ValueError(
                f"register_tools response item at index {idx} must be an object"
            )

        item_dict = cast(dict[str, Any], item)

        tool_id = item_dict.get("id")
        tool_name = item_dict.get("name")
        if not isinstance(tool_id, str) or not tool_id:
            raise ValueError(
                f"register_tools response item at index {idx} is missing a valid id"
            )
        if not isinstance(tool_name, str) or not tool_name:
            raise ValueError(
                f"register_tools response item at index {idx} is missing a valid name"
            )

        tool_name_to_tool[tool_name] = cast(ToolRegistryItem, item_dict)

    return tool_name_to_tool


def _build_register_tools_endpoint(api_base_url: str) -> str:
    return urljoin(api_base_url, "/tool-registry/register-tools")


def _build_register_tools_payload(
    properties: Iterable[RegisterToolProperties],
) -> dict[str, Any]:
    return {"properties": [property.to_api_payload() for property in properties]}


def _build_register_tools_headers(access_token: str) -> dict[str, str]:
    return {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json",
    }


def _execute_register_tools_request(
    *,
    properties: Iterable[RegisterToolProperties],
    api_base_url: str,
    access_token: str,
) -> dict[str, ToolRegistryItem]:
    """Shared implementation for sync + async register_tools.

    All pre-processing and error handling lives here so the sync/async wrappers are
    thin and duplication stays minimal.
    """

    props_list = list(properties)

    # Return early for empty input (avoid a network call).
    if not props_list:
        return {}

    endpoint = _build_register_tools_endpoint(api_base_url)
    payload = _build_register_tools_payload(props_list)
    headers = _build_register_tools_headers(access_token)
    props_count = len(props_list)

    try:
        logger.debug("Registering %d tools", props_count)
        response = requests.post(
            endpoint,
            json=payload,
            headers=headers,
            timeout=REQUEST_TIMEOUT_SECONDS,
        )
        response.raise_for_status()
        response_json = response.json()
        tool_name_to_tool = _parse_register_tools_response(response_json)

        logger.info("Registered %d tools", props_count)
        return tool_name_to_tool

    except requests.exceptions.HTTPError as e:
        status = getattr(getattr(e, "response", None), "status_code", "unknown")
        text = getattr(getattr(e, "response", None), "text", "")
        logger.error(f"Failed to register tools: HTTP {status} - {text}")
        return {}
    except requests.exceptions.RequestException as e:
        logger.error(f"Failed to register tools: {e}")
        return {}
    except (ValueError, TypeError) as e:
        logger.error(f"Failed to parse register tools response: {e}")
        return {}


def register_tools(
    *,
    properties: Iterable[RegisterToolProperties],
    api_base_url: str,
    access_token: str,
) -> dict[str, ToolRegistryItem]:
    """Synchronous tool registration.

    Calls `POST <api_base_url>/tool-registry/register-tools` with JSON body:
    `{ "properties": <properties_array> }`.

    Auth:
      Uses Bearer access token auth (same pattern as other SDK API calls).
    """

    return _execute_register_tools_request(
        properties=properties,
        api_base_url=api_base_url,
        access_token=access_token,
    )


async def aregister_tools(
    *,
    properties: Iterable[RegisterToolProperties],
    api_base_url: str,
    access_token: str,
) -> dict[str, ToolRegistryItem]:
    """Asynchronous tool registration.

    This is a thin async wrapper around the shared synchronous implementation.
    """

    return await asyncio.to_thread(
        _execute_register_tools_request,
        properties=properties,
        api_base_url=api_base_url,
        access_token=access_token,
    )


class _ToolRegistry:
    """In-memory registry of registered tool objects keyed by tool name."""

    def __init__(self, *, api_base_url: str, access_token: str):
        self._api_base_url = api_base_url
        self._access_token = access_token
        self._tool_name_to_tool: dict[str, ToolRegistryItem] = {}

    def update_access_token(self, access_token: str) -> None:
        self._access_token = access_token

    def register_tools(
        self, *, properties: Iterable[RegisterToolProperties]
    ) -> dict[str, ToolRegistryItem]:
        tool_name_to_tool = register_tools(
            properties=properties,
            api_base_url=self._api_base_url,
            access_token=self._access_token,
        )
        self._tool_name_to_tool.update(tool_name_to_tool)
        return dict(tool_name_to_tool)

    def get_registered_tool(self, tool_name: str) -> ToolRegistryItem | None:
        return self._tool_name_to_tool.get(tool_name)

    def as_dict(self) -> dict[str, ToolRegistryItem]:
        return dict(self._tool_name_to_tool)
