from __future__ import annotations

import os
from typing import Any, Dict, Optional

import requests

from .errors import (
    AuthenticationError,
    ForbiddenError,
    NotFoundError,
    OptropicError,
    RateLimitError,
    ValidationError,
)


class Optropic:
    """Client for the Optropic Trust API.

    Usage::

        from optropic import Optropic

        client = Optropic(api_key="optr_live_...")
        asset = client.assets.create(CreateAssetParams(product_name="Widget"))
    """

    def __init__(
        self,
        api_key: Optional[str] = None,
        base_url: str = "https://api.optropic.com",
    ) -> None:
        api_key = api_key or os.environ.get("OPTROPIC_API_KEY")
        if not api_key:
            raise ValueError(
                "api_key is required — pass it directly or set the "
                "OPTROPIC_API_KEY environment variable"
            )

        self._api_key = api_key
        self._base_url = base_url.rstrip("/")
        self._session = requests.Session()
        self._session.headers.update(
            {
                "X-API-Key": self._api_key,
                "Content-Type": "application/json",
                "Accept": "application/json",
            }
        )

        # Lazy-initialised resource namespaces
        self._assets: Optional[Any] = None
        self._compliance: Optional[Any] = None
        self._webhooks: Optional[Any] = None
        self._audit: Optional[Any] = None
        self._keys: Optional[Any] = None

    # ------------------------------------------------------------------
    # Resource properties (lazy)
    # ------------------------------------------------------------------

    @property
    def assets(self) -> Any:
        if self._assets is None:
            from .resources.assets import Assets

            self._assets = Assets(self)
        return self._assets

    @property
    def compliance(self) -> Any:
        if self._compliance is None:
            from .resources.compliance import Compliance

            self._compliance = Compliance(self)
        return self._compliance

    @property
    def webhooks(self) -> Any:
        if self._webhooks is None:
            from .resources.webhooks import Webhooks

            self._webhooks = Webhooks(self)
        return self._webhooks

    @property
    def audit(self) -> Any:
        if self._audit is None:
            from .resources.audit import Audit

            self._audit = Audit(self)
        return self._audit

    @property
    def keys(self) -> Any:
        if self._keys is None:
            from .resources.keys import Keys

            self._keys = Keys(self)
        return self._keys

    # ------------------------------------------------------------------
    # Internal HTTP helpers
    # ------------------------------------------------------------------

    def _request(
        self,
        method: str,
        path: str,
        *,
        json: Optional[Dict[str, Any]] = None,
        params: Optional[Dict[str, Any]] = None,
    ) -> Any:
        """Send a request to the Optropic API and return the parsed JSON body.

        Raises a typed :class:`OptropicError` subclass on non-2xx responses.
        """
        url = f"{self._base_url}/v1{path}"

        # Strip None values from query params
        if params:
            params = {k: v for k, v in params.items() if v is not None}

        # Strip None values from JSON body
        if json:
            json = {k: v for k, v in json.items() if v is not None}

        response = self._session.request(
            method,
            url,
            json=json,
            params=params,
        )

        if response.ok:
            if response.status_code == 204:
                return {}
            return response.json()

        self._raise_for_status(response)

    def _request_raw(
        self,
        method: str,
        path: str,
    ) -> requests.Response:
        """Send a request and return the raw response (for binary endpoints)."""
        url = f"{self._base_url}/v1{path}"
        response = self._session.request(
            method,
            url,
            headers={"Accept": "application/octet-stream"},
        )
        if not response.ok:
            self._raise_for_status(response)
        return response

    @staticmethod
    def _raise_for_status(response: requests.Response) -> None:
        """Map an HTTP error response to a typed SDK exception."""
        try:
            body = response.json()
        except Exception:
            body = {}

        message = body.get("error", body.get("message", response.text))
        code = body.get("code")
        status = response.status_code

        error_map: Dict[int, type] = {
            400: ValidationError,
            401: AuthenticationError,
            403: ForbiddenError,
            404: NotFoundError,
            429: RateLimitError,
        }

        exc_cls = error_map.get(status, OptropicError)
        raise exc_cls(message=message, code=code)
