"""
Y Build Client - Main entry point for SDK.
"""

import time

import httpx
from typing import Optional, Dict, Any, List
from urllib.parse import urljoin

from .models import ContainerInfo, ServiceInfo
from .container import Container
from . import __version__
from .exceptions import (
    YBuildError,
    AuthenticationError,
    QuotaExceededError,
    RateLimitError,
    NetworkError,
    TimeoutError,
    ValidationError,
    ServerError,
)


class YBuildClient:
    """
    Y Build Runtime API Client.

    Example:
        client = YBuildClient(
            api_url="https://api.ybuild.dev",
            api_key="ybld_your_api_key"
        )

        # Create container
        container = client.create_container(thread_id="my-thread")

        # Use container
        result = container.exec("echo hello")
        print(result.stdout)

        # Clean up
        container.stop()
    """

    def __init__(
        self,
        api_url: str,
        api_key: Optional[str] = None,
        timeout: float = 30.0,
        max_retries: int = 3,
    ):
        """
        Initialize Y Build client.

        Args:
            api_url: Base URL of Y Build API (e.g., "https://api.ybuild.dev")
            api_key: API key for authentication (format: ybld_xxx)
            timeout: Default request timeout in seconds
            max_retries: Number of retries for failed requests
        """
        self.api_url = api_url.rstrip("/")
        self.api_key = api_key
        self.timeout = timeout
        self.max_retries = max_retries

        # Build headers
        headers = {
            "Content-Type": "application/json",
            "User-Agent": f"ybuild-python-sdk/{__version__}",
        }
        if api_key:
            headers["Authorization"] = f"Bearer {api_key}"

        # Create HTTP client
        self._client = httpx.Client(
            base_url=self.api_url,
            headers=headers,
            timeout=httpx.Timeout(timeout),
        )

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

    def close(self):
        """Close the HTTP client."""
        self._client.close()

    # ==================== Low-level request ====================

    def _request(
        self,
        method: str,
        path: str,
        **kwargs,
    ) -> Dict[str, Any]:
        """
        Make HTTP request with error handling and retries.

        Args:
            method: HTTP method (GET, POST, etc.)
            path: API path (e.g., "/api/containers/create")
            **kwargs: Additional arguments for httpx.request()

        Returns:
            Parsed JSON response

        Raises:
            YBuildError: On API errors
        """
        last_error = None

        for attempt in range(self.max_retries):
            try:
                response = self._client.request(method, path, **kwargs)
                return self._handle_response(response)

            except httpx.TimeoutException as e:
                last_error = TimeoutError(f"Request timed out: {e}")

            except httpx.NetworkError as e:
                last_error = NetworkError(f"Network error: {e}")

            except YBuildError:
                raise  # Don't retry API errors

            except Exception as e:
                last_error = YBuildError(f"Unexpected error: {e}")

            # Exponential backoff: 0.5s, 1s, 2s, ...
            if attempt < self.max_retries - 1:
                time.sleep(0.5 * (2 ** attempt))

        raise last_error

    def _handle_response(self, response: httpx.Response) -> Dict[str, Any]:
        """Handle HTTP response and raise appropriate exceptions."""
        try:
            data = response.json()
        except Exception:
            data = {"detail": response.text}

        if response.is_success:
            return data

        status = response.status_code
        message = data.get("detail") or data.get("message") or str(data)

        if status == 401:
            raise AuthenticationError(message, status_code=status, response=data)
        elif status == 403:
            if "quota" in message.lower():
                raise QuotaExceededError(message, status_code=status, response=data)
            raise AuthenticationError(message, status_code=status, response=data)
        elif status == 404:
            raise YBuildError(message, status_code=status, response=data)
        elif status == 422:
            raise ValidationError(message, status_code=status, response=data)
        elif status == 429:
            retry_after = response.headers.get("Retry-After")
            raise RateLimitError(
                message,
                status_code=status,
                response=data,
                retry_after=int(retry_after) if retry_after else None,
            )
        elif status >= 500:
            raise ServerError(message, status_code=status, response=data)
        else:
            raise YBuildError(message, status_code=status, response=data)

    # ==================== Service Discovery ====================

    def discover(self) -> ServiceInfo:
        """
        Get service discovery information.

        Returns:
            ServiceInfo with API version, endpoints, and features
        """
        data = self._request("GET", "/api/v1/discover")
        return ServiceInfo.from_dict(data)

    def health(self) -> Dict[str, Any]:
        """
        Check API health.

        Returns:
            Health status dict
        """
        return self._request("GET", "/health")

    # ==================== Container Management ====================

    def create_container(
        self,
        thread_id: str,
        *,
        user_id: Optional[str] = None,
        cpu_limit: float = 1.0,
        memory_limit: int = 4 * 1024 * 1024 * 1024,  # 4GB
        storage_limit: int = 20 * 1024 * 1024 * 1024,  # 20GB
        timeout_seconds: int = 7200,  # 2 hours
        image: Optional[str] = None,
        backend_url: Optional[str] = None,
        internal_api_key: Optional[str] = None,
        env: Optional[Dict[str, str]] = None,
        expose_ports: Optional[List[int]] = None,
    ) -> Container:
        """
        Create a new container.

        Args:
            thread_id: Unique thread/session identifier
            user_id: User ID for cross-thread shared volume at /shared/ (optional)
            cpu_limit: CPU cores (default: 1.0)
            memory_limit: Memory in bytes (default: 4GB)
            storage_limit: Storage in bytes (default: 20GB)
            timeout_seconds: Idle timeout (default: 2 hours)
            image: Container image (uses default if not specified)
            backend_url: Callback URL for container events
            internal_api_key: API key for callback authentication
            env: Additional environment variables
            expose_ports: Ports to expose (default: [3000, 5173, 8080])

        Returns:
            Container instance for interacting with the container
        """
        payload = {
            "thread_id": thread_id,
            "cpu_limit": cpu_limit,
            "memory_limit": memory_limit,
            "storage_limit": storage_limit,
            "timeout_seconds": timeout_seconds,
        }

        if user_id:
            payload["user_id"] = user_id
        if image:
            payload["image"] = image
        if backend_url:
            payload["backend_url"] = backend_url
        if internal_api_key:
            payload["internal_api_key"] = internal_api_key
        if env:
            payload["env"] = env
        if expose_ports is not None:
            payload["expose_ports"] = expose_ports

        data = self._request("POST", "/api/containers/create", json=payload)
        info = ContainerInfo.from_dict(data)

        return Container(self, info)

    def get_container(self, container_id: str) -> Container:
        """
        Get an existing container by ID.

        Args:
            container_id: Container ID

        Returns:
            Container instance
        """
        data = self._request("GET", f"/api/containers/{container_id}/status")
        info = ContainerInfo.from_dict(data)
        return Container(self, info)

    def get_container_by_thread(self, thread_id: str) -> Optional[Container]:
        """
        Get container by thread ID.

        Args:
            thread_id: Thread/session identifier

        Returns:
            Container instance or None if not found
        """
        try:
            data = self._request("GET", f"/api/containers/by-thread/{thread_id}")
            if not data or data.get("status") == "not_found":
                return None
            info = ContainerInfo.from_dict(data)
            return Container(self, info)
        except YBuildError as e:
            if e.status_code == 404:
                return None
            raise

    def list_containers(
        self,
        status: Optional[str] = None,
        limit: int = 100,
        offset: int = 0,
    ) -> List[ContainerInfo]:
        """
        List containers.

        Args:
            status: Filter by status (running, stopped, etc.)
            limit: Maximum results
            offset: Pagination offset

        Returns:
            List of ContainerInfo
        """
        params = {"limit": limit, "offset": offset}
        if status:
            params["status"] = status

        data = self._request("GET", "/api/containers", params=params)
        containers = data.get("containers", data) if isinstance(data, dict) else data

        return [ContainerInfo.from_dict(c) for c in containers]

    # ==================== Organization/Billing ====================

    def get_usage(self) -> Dict[str, Any]:
        """
        Get current usage and billing information.

        Returns:
            Usage statistics including container count, compute time, etc.
        """
        return self._request("GET", "/api/billing/usage")

    def get_quota(self) -> Dict[str, Any]:
        """
        Get organization quota limits.

        Returns:
            Quota information
        """
        return self._request("GET", "/api/billing/quota")
