"""Bittensor wallet authentication helper for ORO SDK.

This module provides utilities to authenticate API requests using
Bittensor wallet signatures (SR25519).
"""

from __future__ import annotations

import time
import uuid
from typing import TYPE_CHECKING, Any
from urllib.parse import urlparse

import httpx
from attrs import define, field

from .retry import AsyncRetryTransport, RetryConfig, RetryTransport

if TYPE_CHECKING:
    from bittensor_wallet import Wallet


def is_public_endpoint(url: str | None) -> bool:
    """Check if a URL path is an unauthenticated endpoint that doesn't require signing.

    Unauthenticated endpoints include:
    - /v1/public/* - All public API routes
    - /v1/auth/* - Authentication flow endpoints (challenge, session, logout)
    - /health - Health check endpoint

    Args:
        url: The URL or path to check.

    Returns:
        True if the endpoint doesn't require signing, False otherwise.
    """
    if not url:
        return False

    # Extract pathname from full URL or use as-is if already a path
    try:
        parsed = urlparse(url)
        pathname = parsed.path or url
    except Exception:
        pathname = url

    # Check for unauthenticated endpoints
    return "/v1/public/" in pathname or "/v1/auth/" in pathname or pathname == "/health" or pathname.endswith("/health")


def generate_auth_headers(wallet: Wallet, nonce: str | None = None) -> dict[str, str]:
    """Generate authentication headers for a Bittensor wallet.

    Args:
        wallet: Bittensor wallet with hotkey for signing.
        nonce: Optional nonce (defaults to UUID4).

    Returns:
        Dictionary with X-Hotkey, X-Signature, X-Nonce, X-Timestamp headers.

    Note:
        The message format is '{hotkey}:{timestamp}:{nonce}' to match
        the ORO Backend's expected signature format.
    """
    if nonce is None:
        nonce = str(uuid.uuid4())

    hotkey = wallet.hotkey.ss58_address
    timestamp = str(int(time.time()))
    message = f"{hotkey}:{timestamp}:{nonce}"

    # Sign the message with the hotkey
    signature = wallet.hotkey.sign(message.encode()).hex()

    return {
        "X-Hotkey": hotkey,
        "X-Signature": f"0x{signature}",
        "X-Nonce": nonce,
        "X-Timestamp": timestamp,
    }


class SigningTransport(httpx.BaseTransport):
    """httpx transport that signs authenticated requests with Bittensor wallet.

    This transport intercepts requests and adds fresh authentication
    headers for non-public endpoints before passing to the underlying transport.

    Public endpoints (/v1/public/*, /health) are passed through without signing.

    Example:
        >>> from oro_sdk import Client
        >>> from oro_sdk.bittensor_auth import SigningTransport
        >>>
        >>> transport = SigningTransport(wallet=wallet)
        >>> client = Client(base_url="https://api.oro.ai", httpx_args={"transport": transport})
    """

    def __init__(self, wallet: Wallet, wrapped: httpx.BaseTransport | None = None):
        self._wallet = wallet
        self._wrapped = wrapped or httpx.HTTPTransport(
            http2=False,
            retries=1,
            limits=httpx.Limits(
                max_connections=10,
                max_keepalive_connections=5,
                keepalive_expiry=30,
            ),
        )

    def handle_request(self, request: httpx.Request) -> httpx.Response:
        # Skip signing for public endpoints
        if not is_public_endpoint(str(request.url)):
            auth_headers = generate_auth_headers(self._wallet)
            for key, value in auth_headers.items():
                request.headers[key] = value
        return self._wrapped.handle_request(request)


class AsyncSigningTransport(httpx.AsyncBaseTransport):
    """Async httpx transport that signs authenticated requests with Bittensor wallet.

    Public endpoints (/v1/public/*, /health) are passed through without signing.
    """

    def __init__(self, wallet: Wallet, wrapped: httpx.AsyncBaseTransport | None = None):
        self._wallet = wallet
        self._wrapped = wrapped or httpx.AsyncHTTPTransport(
            http2=False,
            retries=1,
            limits=httpx.Limits(
                max_connections=10,
                max_keepalive_connections=5,
                keepalive_expiry=30,
            ),
        )

    async def handle_async_request(self, request: httpx.Request) -> httpx.Response:
        # Skip signing for public endpoints
        if not is_public_endpoint(str(request.url)):
            auth_headers = generate_auth_headers(self._wallet)
            for key, value in auth_headers.items():
                request.headers[key] = value
        return await self._wrapped.handle_async_request(request)


@define
class BittensorAuthClient:
    """HTTP client that automatically signs requests with a Bittensor wallet.

    This client is compatible with the generated API functions and adds fresh
    authentication headers to each request via a custom transport.

    Example:
        >>> from bittensor_wallet import Wallet
        >>> from oro_sdk import BittensorAuthClient
        >>> from oro_sdk.api.validator import claim_work
        >>>
        >>> wallet = Wallet(name="validator", hotkey="default")
        >>> client = BittensorAuthClient(
        ...     base_url="https://api.oro.ai",
        ...     wallet=wallet,
        ... )
        >>> work = claim_work.sync(client=client)
    """

    _base_url: str = field(alias="base_url")
    _wallet: Wallet = field(alias="wallet")
    _timeout: httpx.Timeout | None = field(default=None, kw_only=True, alias="timeout")
    _verify_ssl: bool = field(default=True, kw_only=True, alias="verify_ssl")
    _headers: dict[str, str] = field(factory=dict, kw_only=True, alias="headers")
    _retry_config: RetryConfig = field(factory=RetryConfig, kw_only=True, alias="retry_config")
    _client: httpx.Client | None = field(default=None, init=False)
    _async_client: httpx.AsyncClient | None = field(default=None, init=False)

    # Required for compatibility with generated API functions
    raise_on_unexpected_status: bool = field(default=False, kw_only=True)

    def get_httpx_client(self) -> httpx.Client:
        """Get the underlying httpx.Client with signing and retry transports.

        The returned client will automatically add Bittensor auth headers
        to every request and retry transient failures.

        Connection pool is configured with keepalive expiry to prevent
        stale connections from persisting after backend restarts.
        """
        if self._client is None:
            signing = SigningTransport(self._wallet)
            transport = RetryTransport(self._retry_config, wrapped=signing)
            self._client = httpx.Client(
                base_url=self._base_url,
                timeout=self._timeout,
                verify=self._verify_ssl,
                headers=self._headers,
                transport=transport,
            )
        return self._client

    def get_async_httpx_client(self) -> httpx.AsyncClient:
        """Get the underlying httpx.AsyncClient with signing and retry transports.

        The returned client will automatically add Bittensor auth headers
        to every request and retry transient failures.

        Connection pool is configured with keepalive expiry to prevent
        stale connections from persisting after backend restarts.
        """
        if self._async_client is None:
            signing = AsyncSigningTransport(self._wallet)
            transport = AsyncRetryTransport(self._retry_config, wrapped=signing)
            self._async_client = httpx.AsyncClient(
                base_url=self._base_url,
                timeout=self._timeout,
                verify=self._verify_ssl,
                headers=self._headers,
                transport=transport,
            )
        return self._async_client

    def __enter__(self) -> BittensorAuthClient:
        self.get_httpx_client().__enter__()
        return self

    def __exit__(self, *args: Any, **kwargs: Any) -> None:
        self.get_httpx_client().__exit__(*args, **kwargs)

    async def __aenter__(self) -> BittensorAuthClient:
        await self.get_async_httpx_client().__aenter__()
        return self

    async def __aexit__(self, *args: Any, **kwargs: Any) -> None:
        await self.get_async_httpx_client().__aexit__(*args, **kwargs)
