"""Core HTTP client for D365 Commerce Scale Unit APIs."""

from __future__ import annotations

from typing import Any
from urllib.parse import urlencode

import requests

from d365_csu.auth import AzureADAuth, RetailServerAuth


class CSUError(Exception):
    """Raised when a CSU API call fails."""

    def __init__(self, status_code: int, message: str, response: requests.Response | None = None) -> None:
        self.status_code = status_code
        self.response = response
        super().__init__(f"CSU API error {status_code}: {message}")


class CSUClient:
    """HTTP client for Commerce Scale Unit REST APIs."""

    def __init__(
        self,
        base_url: str,
        oun: str,
        auth: AzureADAuth | RetailServerAuth,
        *,
        timeout: int = 30,
    ) -> None:
        self.base_url = base_url.rstrip("/")
        self.oun = oun
        self.auth = auth
        self.timeout = timeout
        self._session = requests.Session()

    @property
    def _headers(self) -> dict[str, str]:
        return self.auth.get_headers(self.oun)

    # ── OData query helpers ──────────────────────────────────────────

    @staticmethod
    def build_odata_params(
        *,
        top: int | None = None,
        skip: int | None = None,
        filter: str | None = None,
        select: str | None = None,
        orderby: str | None = None,
    ) -> dict[str, str]:
        """Build an OData query-string parameter dict."""
        params: dict[str, str] = {}
        if top is not None:
            params["$top"] = str(top)
        if skip is not None:
            params["$skip"] = str(skip)
        if filter is not None:
            params["$filter"] = filter
        if select is not None:
            params["$select"] = select
        if orderby is not None:
            params["$orderby"] = orderby
        return params

    @staticmethod
    def build_query_result_settings(top: int = 10, skip: int = 0) -> dict[str, Any]:
        """Build a QueryResultSettings body for POST-based pagination."""
        return {
            "QueryResultSettings": {
                "Paging": {
                    "Top": top,
                    "Skip": skip,
                },
            },
        }

    # ── HTTP verbs ───────────────────────────────────────────────────

    def _url(self, path: str, params: dict[str, str] | None = None) -> str:
        url = f"{self.base_url}/{path.lstrip('/')}"
        if params:
            url = f"{url}?{urlencode(params)}"
        return url

    def _handle_response(self, resp: requests.Response) -> Any:
        if resp.status_code >= 400:
            try:
                detail = resp.json()
            except ValueError:
                detail = resp.text
            raise CSUError(resp.status_code, str(detail), response=resp)
        if resp.status_code == 204:
            return None
        try:
            return resp.json()
        except ValueError:
            return resp.text

    def get(self, path: str, *, params: dict[str, str] | None = None) -> Any:
        resp = self._session.get(
            self._url(path, params),
            headers=self._headers,
            timeout=self.timeout,
        )
        return self._handle_response(resp)

    def post(self, path: str, body: Any = None, *, params: dict[str, str] | None = None) -> Any:
        resp = self._session.post(
            self._url(path, params),
            headers=self._headers,
            json=body,
            timeout=self.timeout,
        )
        return self._handle_response(resp)

    def patch(self, path: str, body: Any = None) -> Any:
        resp = self._session.patch(
            self._url(path),
            headers=self._headers,
            json=body,
            timeout=self.timeout,
        )
        return self._handle_response(resp)

    def delete(self, path: str) -> Any:
        resp = self._session.delete(
            self._url(path),
            headers=self._headers,
            timeout=self.timeout,
        )
        return self._handle_response(resp)
