from __future__ import annotations

from typing import Any, Dict, Mapping, Optional

import httpx

from ..errors import ApiError, AuthenticationError, RateLimitError
from ..types import (
    MarketplaceListResponse,
    MarketplaceRecipeDetail,
    MarketplaceSearchResponse,
)
from .runs import RunsResource, _extract_run_id, _safe_json


class MarketplaceResource:
    def __init__(self, client: httpx.Client, runs: RunsResource) -> None:
        self._client = client
        self._runs = runs

    def list(
        self,
        *,
        visibility: str | None = None,
        is_composable: bool | None = None,
        category: str | None = None,
        page: int | None = None,
        page_size: int | None = None,
    ) -> MarketplaceListResponse:
        params = self._list_params(
            visibility=visibility,
            is_composable=is_composable,
            category=category,
            page=page,
            page_size=page_size,
        )
        return self._json(self._client.get("marketplace/recipes/", params=params))

    def search(
        self,
        query: str,
        *,
        visibility: str | None = None,
        is_composable: bool | None = None,
        category: str | None = None,
        page: int | None = None,
        page_size: int | None = None,
    ) -> MarketplaceSearchResponse:
        q = query.strip()
        if not q:
            raise ValueError("query must be a non-empty string")

        params = {"q": q}
        params.update(
            self._list_params(
                visibility=visibility,
                is_composable=is_composable,
                category=category,
                page=page,
                page_size=page_size,
            )
        )
        return self._json(self._client.get("marketplace/recipes/search/", params=params))

    def detail(self, recipe_id: str) -> MarketplaceRecipeDetail:
        rid = recipe_id.strip()
        if not rid:
            raise ValueError("recipe_id must be a non-empty string")
        return self._json(self._client.get(f"marketplace/recipes/{rid}/"))

    def run(
        self,
        recipe_id: str,
        *,
        inputs: Optional[Mapping[str, str]] = None,
        revision_sha: str | None = None,
        wait: bool = True,
        timeout: float = 300.0,
        poll_interval: float = 0.5,
        max_poll_interval: float = 5.0,
    ) -> Dict[str, Any]:
        rid = recipe_id.strip()
        if not rid:
            raise ValueError("recipe_id must be a non-empty string")

        payload: Dict[str, Any] = {}
        if inputs:
            payload["inputs"] = dict(inputs)
        if revision_sha and revision_sha.strip():
            payload["revision_sha"] = revision_sha.strip()

        response = self._json(
            self._client.post(f"marketplace/recipes/{rid}/runs/", json=payload)
        )
        if not wait:
            return response

        run_id = _extract_run_id(response)
        if not run_id:
            return response

        return self._runs.wait(
            run_id=run_id,
            timeout=timeout,
            poll_interval=poll_interval,
            max_poll_interval=max_poll_interval,
        )

    @staticmethod
    def _list_params(
        *,
        visibility: str | None,
        is_composable: bool | None,
        category: str | None,
        page: int | None,
        page_size: int | None,
    ) -> Dict[str, Any]:
        params: Dict[str, Any] = {}
        if visibility and visibility.strip():
            params["visibility"] = visibility.strip()
        if is_composable is not None:
            params["is_composable"] = "true" if is_composable else "false"
        if category and category.strip():
            params["category"] = category.strip()
        if page is not None:
            params["page"] = page
        if page_size is not None:
            params["page_size"] = page_size
        return params

    @staticmethod
    def _json(res: httpx.Response) -> Dict[str, Any]:
        if res.status_code in (401, 403):
            raise AuthenticationError(
                status_code=res.status_code,
                body=_safe_json(res),
                message="Unauthorized",
            )
        if res.status_code == 429:
            err = RateLimitError(
                status_code=429, body=_safe_json(res), message="Rate limited"
            )
            err.retry_after = res.headers.get("Retry-After")
            raise err
        if res.status_code < 200 or res.status_code >= 300:
            raise ApiError(status_code=res.status_code, body=_safe_json(res))

        data = _safe_json(res)
        return data if isinstance(data, dict) else {"data": data}
