"""Basket (Hyper) resource for the NemlAI SDK."""

from __future__ import annotations

import time
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union

if TYPE_CHECKING:
    from nemlai.client import NemlAI


class BasketResource:
    """Manage the smart basket via the Hyper endpoints.

    All methods support an optional ``structured`` parameter.  When
    ``structured=True``, the method calls the v2 JSON endpoint and
    returns a ``dict`` (or typed dataclass) instead of a plain string.
    """

    _VALID_SIZES = {"small", "medium", "large", "xl", "full"}

    def __init__(self, client: "NemlAI") -> None:
        self._client = client

    # -- v1 text endpoints (backward-compatible) ----------------------------

    def status(self) -> str:
        """GET ``/cli/api/hyper/status`` -- basket unlock status."""
        return self._client._get("/cli/api/hyper/status")

    def generate(
        self,
        size: str = "medium",
        date: Optional[str] = None,
        items: Optional[str] = None,
        structured: bool = False,
    ) -> Union[str, Dict[str, Any]]:
        """Generate a smart basket (ML prediction).

        When ``structured=True``, starts the job and polls until complete,
        returning structured items.
        """
        if size not in self._VALID_SIZES:
            raise ValueError(f"Invalid size: {size!r}. Must be one of: {', '.join(sorted(self._VALID_SIZES))}")

        if not structured:
            payload: dict = {"size": size}
            if date is not None:
                payload["date"] = date
            if items is not None:
                payload["items"] = items
            return self._client._post("/cli/api/hyper/generate", json=payload)

        # Structured: use v2 async endpoint with polling
        payload = {"size": size}
        if date is not None:
            payload["date"] = date
        if items is not None:
            payload["items"] = items

        resp = self._client._request_json("POST", "/cli/api/v2/basket/generate", json=payload)
        job_id = resp.get("job_id")
        if not job_id:
            from nemlai.client import NemlAIError
            raise NemlAIError("No job_id in generate response")

        return self._poll_generate(job_id)

    def _poll_generate(self, job_id: str, timeout: float = 600.0) -> Dict[str, Any]:
        """Poll basket generation until complete or timeout."""
        from nemlai.client import NemlAIError

        start = time.monotonic()
        while time.monotonic() - start < timeout:
            resp = self._client._request_json("GET", f"/cli/api/v2/basket/generate/status/{job_id}")
            status = resp.get("status", "unknown")
            if status == "completed":
                return resp
            if status == "failed":
                raise NemlAIError(resp.get("error", "Generation failed"))
            time.sleep(2)

        raise NemlAIError("Basket generation timed out")

    def items(self, structured: bool = False) -> Union[str, Dict[str, Any]]:
        """List items in the current basket.

        When ``structured=True``, returns a dict with ``items``, ``total_price``, ``item_count``.
        """
        if structured:
            return self._client._request_json("GET", "/cli/api/v2/basket/items")
        return self._client._get("/cli/api/hyper/items")

    def add(
        self,
        query: str,
        structured: bool = False,
        product_number: Optional[str] = None,
        quantity: int = 1,
    ) -> Union[str, Dict[str, Any]]:
        """Add an item to the basket.

        When ``structured=True``, calls v2 endpoint and returns structured result.
        Can accept ``product_number`` for direct SKU lookup (v2 only).
        """
        if structured:
            payload: Dict[str, Any] = {"quantity": quantity}
            if product_number:
                payload["product_number"] = product_number
            else:
                payload["query"] = query
            return self._client._request_json("POST", "/cli/api/v2/basket/add", json=payload)

        if not query or not query.strip():
            raise ValueError("Search query must be non-empty")
        return self._client._post("/cli/api/hyper/add", json={"query": query})

    def remove(
        self,
        item: str,
        structured: bool = False,
    ) -> Union[str, Dict[str, Any]]:
        """Remove an item from the basket.

        When ``structured=True``, ``item`` is treated as a ``product_number``.
        """
        if structured:
            return self._client._request_json(
                "POST", "/cli/api/v2/basket/remove",
                json={"product_number": item},
            )

        if not item or not item.strip():
            raise ValueError("Item identifier must be non-empty")
        return self._client._post("/cli/api/hyper/remove", json={"item": item})

    def adjust(
        self,
        item: str,
        quantity: str,
        structured: bool = False,
    ) -> Union[str, Dict[str, Any]]:
        """Adjust item quantity.

        When ``structured=True``, ``item`` is treated as a ``product_number``.
        """
        try:
            q = int(quantity)
        except (ValueError, TypeError):
            raise ValueError(f"Quantity must be a valid integer, got: {quantity!r}")
        if q < 0:
            raise ValueError("Quantity must be non-negative")

        if structured:
            return self._client._request_json(
                "POST", "/cli/api/v2/basket/adjust",
                json={"product_number": item, "quantity": q},
            )

        if not item or not item.strip():
            raise ValueError("Item identifier must be non-empty")
        return self._client._post(
            "/cli/api/hyper/adjust", json={"item": item, "quantity": quantity}
        )

    def send(
        self,
        structured: bool = False,
        timeout: float = 300.0,
    ) -> Union[str, Dict[str, Any]]:
        """Send basket to Nemlig.

        When ``structured=True``, starts the job and polls until complete,
        returning structured result with ``basket_url``.
        """
        if not structured:
            return self._client._post("/cli/api/hyper/send")

        resp = self._client._request_json("POST", "/cli/api/v2/basket/send")
        job_id = resp.get("job_id")
        if not job_id:
            from nemlai.client import NemlAIError
            raise NemlAIError("No job_id in send response")

        return self._poll_send(job_id, timeout=timeout)

    def _poll_send(self, job_id: str, timeout: float = 300.0) -> Dict[str, Any]:
        """Poll basket send until complete or timeout."""
        from nemlai.client import NemlAIError

        start = time.monotonic()
        while time.monotonic() - start < timeout:
            resp = self._client._request_json("GET", f"/cli/api/v2/basket/send/status/{job_id}")
            status = resp.get("status", "unknown")
            if status == "ready":
                return resp
            if status == "failed":
                raise NemlAIError(resp.get("error", "Send failed"))
            time.sleep(2)

        raise NemlAIError("Basket send timed out")

    def search(
        self,
        query: str,
        structured: bool = False,
        category: Optional[str] = None,
    ) -> Union[str, Dict[str, Any]]:
        """Search for products.

        When ``structured=True``, returns dict with ``products``, ``total``, ``query``.
        Optional ``category`` filter (v2 only).
        """
        if structured:
            payload: Dict[str, Any] = {"query": query}
            if category:
                payload["category"] = category
            return self._client._request_json("POST", "/cli/api/v2/search", json=payload)

        if not query or not query.strip():
            raise ValueError("Search query must be non-empty")
        return self._client._post("/cli/api/hyper/search", json={"query": query})

    def dish(self, query: str) -> str:
        """POST ``/cli/api/hyper/dish`` -- search for a dish via Dify AI."""
        if not query or not query.strip():
            raise ValueError("Dish query must be non-empty")
        return self._client._post("/cli/api/hyper/dish", json={"query": query})

    # -- v2-only methods ----------------------------------------------------

    def add_batch(self, items: List[Dict[str, Any]]) -> Dict[str, Any]:
        """Add multiple products to basket in one request (v2 only).

        Parameters
        ----------
        items:
            List of ``{"product_number": str, "quantity": int}``.

        Returns dict with ``added``, ``failed``, ``duplicates``,
        ``total_price``, ``item_count``.
        """
        return self._client._request_json(
            "POST", "/cli/api/v2/basket/add-batch",
            json={"items": items},
        )

    def categories(self) -> Dict[str, Any]:
        """List available product categories (v2 only).

        Returns dict with ``categories`` list.
        """
        return self._client._request_json("GET", "/cli/api/v2/categories")
