"""Core HTTP client for the NemlAI CLI API."""

from __future__ import annotations

from typing import Any, Dict, Optional, Tuple
from urllib.parse import urlparse

import httpx

from nemlai.basket import BasketResource
from nemlai.household import HouseholdResource
from nemlai.orders import OrdersResource
from nemlai.settings import SettingsResource


def parse_response(text: str) -> Tuple[str, str]:
    """Parse a CLI API response into ``(status, body)``.

    The API always returns::

        STATUS: <ok|error|pending>
        <body>

    Returns a 2-tuple of ``(status_word, remaining_body)``.
    If the text does not match the expected format, status is ``"unknown"``
    and the full text is returned as the body.
    """
    lines = text.split("\n", 1)
    first = lines[0].strip()
    if first.startswith("STATUS: "):
        status = first[len("STATUS: "):]
        body = lines[1] if len(lines) > 1 else ""
        return status, body
    return "unknown", text


class NemlAIError(Exception):
    """Raised when the API returns an error status."""

    def __init__(self, message: str, hint: str = ""):
        self.message = message
        self.hint = hint
        super().__init__(message)


class NemlAI:
    """Python client for the NemlAI CLI API.

    Parameters
    ----------
    token:
        Bearer token for authenticated requests.
    base_url:
        Root URL of the NemlAI server.
    timeout:
        HTTP request timeout in seconds.
    """

    def __init__(
        self,
        token: str = "",
        base_url: str = "https://nemlai.fly.dev",
        timeout: float = 120.0,
    ) -> None:
        self.base_url = base_url.rstrip("/")
        # Enforce HTTPS unless connecting to localhost for development
        if not self.base_url.startswith("https://"):
            parsed = urlparse(self.base_url)
            if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
                raise NemlAIError(
                    "Refusing to connect over non-HTTPS connection. "
                    "Use https:// or connect to localhost for development."
                )
        self.token = token
        headers: Dict[str, str] = {}
        if token:
            headers["Authorization"] = f"Bearer {token}"
        self._http = httpx.Client(
            base_url=self.base_url,
            headers=headers,
            timeout=timeout,
        )
        # Sub-resources
        self.basket = BasketResource(self)
        self.orders = OrdersResource(self)
        self.settings = SettingsResource(self)
        self.household = HouseholdResource(self)

    def __repr__(self) -> str:
        masked = self.token[:4] + "****" if len(self.token) > 4 else "****" if self.token else "(none)"
        return f"NemlAI(base_url={self.base_url!r}, token={masked})"

    # -- Class methods -------------------------------------------------------

    @classmethod
    def login(
        cls,
        email: str,
        password: str,
        base_url: str = "https://nemlai.fly.dev",
        timeout: float = 120.0,
    ) -> "NemlAI":
        """Authenticate and return a new :class:`NemlAI` client.

        POSTs to ``/cli/api/auth/login`` and extracts the token from
        the ``TOKEN <value>`` line in the response body.
        """
        base_url = base_url.rstrip("/")
        if not base_url.startswith("https://"):
            parsed = urlparse(base_url)
            if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
                raise NemlAIError(
                    "Refusing to send credentials over non-HTTPS connection. "
                    "Use https:// or connect to localhost for development."
                )
        tmp = httpx.Client(base_url=base_url, timeout=timeout)
        try:
            resp = tmp.post(
                "/cli/api/auth/login",
                json={"email": email, "password": password},
            )
            text = resp.text
        finally:
            tmp.close()

        status, body = parse_response(text)
        if status != "ok":
            raise NemlAIError(body)

        token = ""
        for line in body.splitlines():
            if line.startswith("TOKEN "):
                token = line[len("TOKEN "):].strip()
                break

        if not token:
            raise NemlAIError("Login succeeded but no token found in response")

        return cls(token=token, base_url=base_url, timeout=timeout)

    # -- Low-level HTTP helpers ---------------------------------------------

    def _request(self, method: str, path: str, **kwargs: Any) -> str:
        """Send an HTTP request and return the raw response text."""
        # Only allow safe kwargs to pass through to httpx
        allowed = {"json", "data", "params", "headers", "content", "files"}
        filtered = {k: v for k, v in kwargs.items() if k in allowed}
        resp = self._http.request(method, path, **filtered)
        if resp.status_code >= 500:
            raise NemlAIError(f"Server error (HTTP {resp.status_code})")
        if resp.status_code == 401:
            raise NemlAIError("Authentication failed — run 'nemlai login' to re-authenticate")
        if resp.status_code == 429:
            raise NemlAIError("Rate limited — please wait before retrying")
        return resp.text

    def _ok_or_raise(self, text: str) -> str:
        """Parse *text*, raise on error, return the body on success."""
        status, body = parse_response(text)
        if status == "error":
            # body may contain "ERROR msg\nHINT hint"
            hint = ""
            msg = body
            for line in body.splitlines():
                if line.startswith("HINT "):
                    hint = line[len("HINT "):]
                elif line.startswith("ERROR "):
                    msg = line[len("ERROR "):]
            raise NemlAIError(msg, hint=hint)
        return body

    def _get(self, path: str) -> str:
        """GET *path* and return the parsed body (or raise)."""
        return self._ok_or_raise(self._request("GET", path))

    def _post(self, path: str, json: Optional[Any] = None) -> str:
        """POST *path* and return the parsed body (or raise)."""
        return self._ok_or_raise(self._request("POST", path, json=json))

    def _put(self, path: str, json: Optional[Any] = None) -> str:
        """PUT *path* and return the parsed body (or raise)."""
        return self._ok_or_raise(self._request("PUT", path, json=json))

    # -- Convenience methods ------------------------------------------------

    def command(self, text: str) -> str:
        """Send a free-form command string to ``/cli/api/command``.

        Returns the raw response text (unparsed).
        """
        return self._request("POST", "/cli/api/command", json={"command": text})

    def whoami(self) -> Dict[str, str]:
        """GET ``/cli/api/auth/me`` and parse ``key: value`` lines into a dict."""
        body = self._get("/cli/api/auth/me")
        result: Dict[str, str] = {}
        for line in body.splitlines():
            if ": " in line:
                key, _, value = line.partition(": ")
                result[key.strip()] = value.strip()
        return result

    # -- Context manager ----------------------------------------------------

    def close(self) -> None:
        """Close the underlying HTTP client."""
        self._http.close()

    def __enter__(self) -> "NemlAI":
        return self

    def __exit__(self, *exc: Any) -> None:
        self.close()
