"""
x402discovery — Runtime service discovery for the x402 payment protocol.

Finds x402-payable API endpoints so autonomous agents can discover and call
paid services without hardcoded URLs.

Quick start:
    from x402discovery import browse, discover

    # Free catalog (no payment needed)
    services = browse()

    # Paid discovery with quality filtering (requires funded Base wallet)
    results = discover(capability="research", max_price_usd=0.10)
"""
from __future__ import annotations

import time
from typing import Any, Dict, List, Optional

import requests

__version__ = "0.1.1"
__all__ = ["browse", "discover", "health_check", "X402DiscoveryClient"]

DISCOVERY_BASE_URL = "https://x402-discovery-api.onrender.com"


def _parse_catalog(data: Any) -> List[Dict]:
    """Parse catalog response regardless of top-level key name."""
    if isinstance(data, list):
        return data
    for key in ("endpoints", "services", "results", "data"):
        if key in data and isinstance(data[key], list):
            return data[key]
    return []


def browse(
    *,
    category: Optional[str] = None,
    max_price_usd: Optional[float] = None,
    query: Optional[str] = None,
    base_url: str = DISCOVERY_BASE_URL,
    timeout: int = 15,
) -> List[Dict]:
    """Return services from the free catalog (no x402 payment required).

    Args:
        category: Filter by category (research, data, compute, monitoring,
                  generation, verification, routing, storage, other).
        max_price_usd: Maximum price per call in USD.
        query: Case-insensitive text search on name + description.
        base_url: Override the discovery API base URL.
        timeout: HTTP timeout in seconds.

    Returns:
        List of service dicts sorted by uptime%, then latency.
    """
    resp = requests.get(f"{base_url}/catalog", timeout=timeout)
    resp.raise_for_status()
    services = _parse_catalog(resp.json())

    if category:
        services = [
            s for s in services
            if s.get("category") == category
            or category in s.get("capability_tags", [])
            or category in s.get("tags", [])
        ]
    if max_price_usd is not None:
        services = [
            s for s in services
            if s.get("price_usd", 999) <= max_price_usd
        ]
    if query:
        q = query.lower()
        services = [
            s for s in services
            if q in s.get("name", "").lower()
            or q in s.get("description", "").lower()
            or q in " ".join(s.get("tags", [])).lower()
        ]

    # Sort: verified_up first, then by uptime%, then latency
    def sort_key(s: Dict):
        status_order = {"verified_up": 0, "unverified": 1, "verified_down": 2}
        return (
            status_order.get(s.get("health_status", "unverified"), 1),
            -s.get("uptime_pct", 0),
            s.get("avg_latency_ms", 9999),
        )

    return sorted(services, key=sort_key)


def discover(
    *,
    capability: Optional[str] = None,
    max_price_usd: float = 0.50,
    query: Optional[str] = None,
    base_url: str = DISCOVERY_BASE_URL,
    timeout: int = 15,
) -> List[Dict]:
    """Discover services via the free catalog.

    An alias for browse() with a capability-focused interface that mirrors
    the paid /discover endpoint's semantics.

    Args:
        capability: What the service should do (research, data, compute, etc.).
        max_price_usd: Max price per call in USD.
        query: Free-text search.
        base_url: Override the discovery API base URL.
        timeout: HTTP timeout in seconds.

    Returns:
        Quality-ranked list of matching service dicts.
    """
    return browse(
        category=capability,
        max_price_usd=max_price_usd,
        query=query,
        base_url=base_url,
        timeout=timeout,
    )


def health_check(
    service_id: str,
    *,
    base_url: str = DISCOVERY_BASE_URL,
    timeout: int = 15,
) -> Dict:
    """Get live health status for a service by ID.

    Args:
        service_id: The service ID (e.g. "legacy/cf-pay-per-crawl").
        base_url: Override the discovery API base URL.
        timeout: HTTP timeout in seconds.

    Returns:
        Dict with health_status, uptime_pct, avg_latency_ms, etc.
    """
    resp = requests.get(f"{base_url}/health/{service_id}", timeout=timeout)
    resp.raise_for_status()
    return resp.json()


class X402DiscoveryClient:
    """Stateful client with caching and convenience methods.

    Example:
        client = X402DiscoveryClient(max_price_usd=0.10)
        services = client.browse(category="research")
        best = client.best(category="research")
    """

    def __init__(
        self,
        *,
        max_price_usd: float = 0.50,
        base_url: str = DISCOVERY_BASE_URL,
        cache_ttl: int = 300,
    ):
        self.max_price_usd = max_price_usd
        self.base_url = base_url
        self.cache_ttl = cache_ttl
        self._cache: Optional[List[Dict]] = None
        self._cache_ts: float = 0

    def _get_catalog(self) -> List[Dict]:
        now = time.time()
        if self._cache is None or now - self._cache_ts > self.cache_ttl:
            self._cache = browse(base_url=self.base_url, max_price_usd=self.max_price_usd)
            self._cache_ts = now
        return self._cache

    def browse(
        self,
        *,
        category: Optional[str] = None,
        query: Optional[str] = None,
    ) -> List[Dict]:
        """Browse cached catalog, optionally filtered."""
        services = self._get_catalog()
        if category:
            services = [
                s for s in services
                if s.get("category") == category
                or category in s.get("capability_tags", [])
            ]
        if query:
            q = query.lower()
            services = [
                s for s in services
                if q in s.get("name", "").lower()
                or q in s.get("description", "").lower()
            ]
        return services

    def best(self, *, category: Optional[str] = None, query: Optional[str] = None) -> Optional[Dict]:
        """Return the best (top-ranked) service or None."""
        results = self.browse(category=category, query=query)
        return results[0] if results else None

    def format_results(self, services: List[Dict], limit: int = 5) -> str:
        """Format services as a human/LLM-readable string."""
        if not services:
            return f"No services found. Catalog: {self.base_url}/catalog"
        lines = [f"Found {len(services)} service(s) (showing top {min(limit, len(services))}):\n"]
        for i, s in enumerate(services[:limit], 1):
            lines.append(
                f"{i}. {s.get('name')} [{s.get('health_status', 'unknown')}]\n"
                f"   ID: {s.get('service_id', s.get('id'))}\n"
                f"   Price: ${s.get('price_usd', '?')}/call\n"
                f"   URL: {s.get('url', s.get('endpoint_url', 'N/A'))}\n"
                f"   {s.get('description', '')}\n"
            )
        return "\n".join(lines)
