"""Lightweight HTTP client for the Skytale API server.

Uses only stdlib (``urllib``) to avoid adding external dependencies.
All methods authenticate with the API key directly — the API server
accepts both ``sk_live_*`` keys and JWTs in the Authorization header.

Usage::

    from skytale_sdk._api import SkytaleAPI

    api = SkytaleAPI("https://api.skytale.sh", "sk_live_...")
    api.register_channel("org/ns/svc")
    resp = api.create_invite("org/ns/svc")
    print(resp["token"])  # "skt_inv_..."
"""

from __future__ import annotations

import json
import logging
import urllib.error
import urllib.parse
import urllib.request
from typing import Any, Dict, Optional

logger = logging.getLogger(__name__)


class SkytaleAPI:
    """HTTP client for Skytale API channel and invite operations.

    Args:
        api_url: Base URL of the API server (e.g. ``https://api.skytale.sh``).
        api_key: API key for authentication (``sk_live_...``).
    """

    def __init__(self, api_url: str, api_key: str) -> None:
        self._base = api_url.rstrip("/")
        self._api_key = api_key

    def _request(
        self,
        method: str,
        path: str,
        body: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, Any]:
        """Send an HTTP request and return the parsed JSON response.

        Args:
            method: HTTP method (GET, POST, etc.).
            path: URL path (appended to the base URL).
            body: Optional JSON body for POST requests.

        Returns:
            Parsed JSON response as a dict.

        Raises:
            RuntimeError: On HTTP errors or connection failures.
        """
        url = f"{self._base}{path}"
        data = json.dumps(body).encode("utf-8") if body else None
        req = urllib.request.Request(url, data=data, method=method)
        req.add_header("Authorization", f"Bearer {self._api_key}")
        req.add_header("Content-Type", "application/json")
        req.add_header("Accept", "application/json")

        try:
            with urllib.request.urlopen(req, timeout=30) as resp:
                return json.loads(resp.read())
        except urllib.error.HTTPError as exc:
            try:
                error_body = json.loads(exc.read())
                msg = error_body.get("error", str(exc))
                code = error_body.get("code", "")
            except (json.JSONDecodeError, UnicodeDecodeError):
                msg = str(exc)
                code = ""
            raise _api_error(exc.code, code, msg) from None
        except urllib.error.URLError as exc:
            from skytale_sdk.errors import TransportError

            raise TransportError(f"API connection error: {exc.reason}") from None

    # ------------------------------------------------------------------
    # Channel registration
    # ------------------------------------------------------------------

    def register_channel(self, name: str) -> Dict[str, Any]:
        """Register a channel with the API (idempotent).

        Args:
            name: Channel name in ``org/namespace/service`` format.

        Returns:
            ``{"id": "uuid", "name": "org/ns/svc"}``
        """
        return self._request("POST", "/v1/channels", {"name": name})

    # ------------------------------------------------------------------
    # Invite tokens
    # ------------------------------------------------------------------

    def create_invite(
        self,
        channel: str,
        max_uses: int = 1,
        ttl_seconds: int = 3600,
    ) -> Dict[str, Any]:
        """Create an invite token for a channel.

        Args:
            channel: Channel name.
            max_uses: Maximum number of times the token can be used.
            ttl_seconds: Token lifetime in seconds.

        Returns:
            ``{"token": "skt_inv_...", "expires_at": "..."}``
        """
        return self._request(
            "POST",
            "/v1/channels/invites",
            {
                "channel": channel,
                "max_uses": max_uses,
                "ttl_seconds": ttl_seconds,
            },
        )

    # ------------------------------------------------------------------
    # Join flow
    # ------------------------------------------------------------------

    def join(
        self,
        channel: str,
        token: str,
        key_package_b64: str,
        identity: str,
    ) -> Dict[str, Any]:
        """Submit a join request with an invite token and key package.

        Args:
            channel: Channel name to join.
            token: Invite token (``skt_inv_...``).
            key_package_b64: Base64-encoded MLS key package.
            identity: Agent identity string.

        Returns:
            ``{"request_id": "uuid"}``
        """
        return self._request(
            "POST",
            "/v1/channels/join",
            {
                "channel": channel,
                "token": token,
                "key_package": key_package_b64,
                "identity": identity,
            },
        )

    def get_pending(self, channel: str) -> Dict[str, Any]:
        """Get pending join requests for a channel.

        Args:
            channel: Channel name.

        Returns:
            ``{"requests": [{"id": "...", "identity": "...",
            "key_package": "...", "created_at": "..."}]}``
        """
        encoded = urllib.parse.quote(channel, safe="")
        return self._request("GET", f"/v1/channels/pending?channel={encoded}")

    def submit_welcome(self, request_id: str, welcome_b64: str) -> Dict[str, Any]:
        """Submit an MLS Welcome message for a pending join request.

        Args:
            request_id: UUID of the join request.
            welcome_b64: Base64-encoded MLS Welcome message.

        Returns:
            ``{"status": "completed"}``
        """
        return self._request(
            "POST",
            "/v1/channels/welcome",
            {"request_id": request_id, "welcome": welcome_b64},
        )

    def get_welcome(self, request_id: str) -> Dict[str, Any]:
        """Poll for the Welcome message (called by the joining agent).

        Args:
            request_id: UUID of the join request.

        Returns:
            ``{"status": "pending"}`` or
            ``{"status": "completed", "welcome": "<base64>"}``
        """
        return self._request("GET", f"/v1/channels/welcome/{request_id}")


def _api_error(http_code: int, error_code: str, message: str) -> Exception:
    """Map an API error code to the appropriate typed exception."""
    from skytale_sdk.errors import (
        AuthError,
        ChannelError,
        QuotaExceededError,
        SkytaleError,
    )

    full_msg = f"API error ({http_code}): {message}"

    if error_code == "quota_exceeded":
        return QuotaExceededError(full_msg)
    if error_code == "unauthorized":
        return AuthError(full_msg)
    if error_code in ("not_found", "bad_request", "conflict"):
        return ChannelError(full_msg)
    return SkytaleError(full_msg)
