from __future__ import annotations

from typing import Any, Dict, Mapping, Optional

import httpx

from ..errors import ApiError, AuthenticationError, RateLimitError
from ..types import RecipeListResponse, RecipeOutputContract, RecipeSummary
from .runs import _safe_json


class RecipesResource:
    def __init__(self, client: httpx.Client) -> None:
        self._client = client

    def list(
        self,
        *,
        visibility: str | None = None,
        listed: bool | None = None,
        q: str | None = None,
        page: int | None = None,
        page_size: int | None = None,
    ) -> RecipeListResponse:
        params: Dict[str, Any] = {}
        if visibility and visibility.strip():
            params["visibility"] = visibility.strip()
        if listed is not None:
            params["listed"] = "true" if listed else "false"
        if q and q.strip():
            params["q"] = q.strip()
        if page is not None:
            params["page"] = page
        if page_size is not None:
            params["page_size"] = page_size
        return self._json(self._client.get("recipes/", params=params))

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

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

    def create(
        self,
        *,
        name: str,
        visibility: str = "private",
        listed: bool = False,
        is_composable: bool = False,
        description: str = "",
        spec: Optional[Mapping[str, Any]] = None,
    ) -> RecipeSummary:
        payload: Dict[str, Any] = {
            "name": name,
            "visibility": visibility,
            "listed": listed,
            "is_composable": is_composable,
            "description": description,
            "spec": dict(spec or {}),
        }
        return self._json(self._client.post("recipes/", json=payload))

    def update(self, recipe_id: str, **fields: Any) -> RecipeSummary:
        rid = recipe_id.strip()
        if not rid:
            raise ValueError("recipe_id must be a non-empty string")
        payload = {k: v for k, v in fields.items() if v is not None}
        return self._json(self._client.patch(f"recipes/{rid}/", json=payload))

    def delete(self, recipe_id: str, *, purge: bool = False) -> Dict[str, Any]:
        rid = recipe_id.strip()
        if not rid:
            raise ValueError("recipe_id must be a non-empty string")
        params = {"purge": "true"} if purge else None
        res = self._client.delete(f"recipes/{rid}/", params=params)
        if res.status_code == 204:
            return {"deleted": True, "recipe_ulid": rid, "purge": purge}
        return self._json(res)

    @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}
