"""Synchronous Baponi client."""

from __future__ import annotations

import os
import warnings
from types import TracebackType
from typing import Self

import httpx

from baponi._base import (
    build_execute_body,
    build_execute_url,
    build_request_timeout,
    default_headers,
    execute_with_retry,
    validate_execute_params,
)
from baponi._constants import DEFAULT_BASE_URL, DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT
from baponi.exceptions import BaponiError
from baponi.types import SandboxResult


class Baponi:
    """Synchronous client for the Baponi sandbox execution API.

    Usage::

        with Baponi() as client:
            result = client.execute("print('hello')")
            print(result.stdout)

    Or without context manager::

        client = Baponi()
        result = client.execute("print('hello')")
        client.close()
    """

    def __init__(
        self,
        api_key: str | None = None,
        *,
        base_url: str = DEFAULT_BASE_URL,
        timeout: float = DEFAULT_TIMEOUT,
        max_retries: int = DEFAULT_MAX_RETRIES,
        http_client: httpx.Client | None = None,
    ) -> None:
        resolved_key = api_key or os.environ.get("BAPONI_API_KEY")
        if not resolved_key:
            raise BaponiError(
                "API key is required. Pass api_key= or set BAPONI_API_KEY environment variable."
            )
        self._api_key = resolved_key
        self._base_url = base_url.rstrip("/")
        self._max_retries = max_retries
        self._execute_url = build_execute_url(self._base_url)
        self._headers = default_headers(self._api_key)

        if not self._base_url.startswith("https://"):
            warnings.warn(
                f"Base URL {self._base_url!r} does not use HTTPS. "
                "API keys will be sent in plaintext.",
                stacklevel=2,
            )

        if http_client is not None:
            self._client = http_client
            self._owns_client = False
        else:
            self._client = httpx.Client(timeout=httpx.Timeout(timeout))
            self._owns_client = True

    def execute(
        self,
        code: str,
        *,
        language: str = "python",
        timeout: int = 30,
        thread_id: str | None = None,
        metadata: dict[str, str] | None = None,
    ) -> SandboxResult:
        """Execute code in a sandboxed environment.

        Args:
            code: The code to execute.
            language: Programming language. One of: python, bash, node,
                ruby, php, deno, bun.
            timeout: Execution timeout in seconds (1-300).
            thread_id: Pass for persistent state (files, pip packages)
                across calls.
            metadata: Key-value metadata for audit trail and filtering.

        Returns:
            SandboxResult with stdout, stderr, exit_code, and timing info.

        Raises:
            ValueError: If input validation fails (client-side).
            AuthenticationError: If the API key is invalid (401).
            RateLimitError: If rate limited (429).
            ThreadBusyError: If the thread is busy (409).
            APITimeoutError: If execution timed out on server (504).
            ServerError: If server error (500/503).
            BaponiError: For any other API error.
        """
        validate_execute_params(code, language, timeout, thread_id, metadata)
        body = build_execute_body(code, language, timeout, thread_id, metadata)
        request_timeout = build_request_timeout(timeout)

        return execute_with_retry(
            self._client,
            url=self._execute_url,
            headers=self._headers,
            body=body,
            timeout=request_timeout,
            max_retries=self._max_retries,
        )

    def close(self) -> None:
        """Close the underlying HTTP client if internally created."""
        if self._owns_client:
            self._client.close()

    def __enter__(self) -> Self:
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        self.close()
