"""
A small HTTP client for the Wisefood data catalog.

Fixes & features:
- Correct URL joining (keeps /api/v1).
- Uses a requests.Session for connection reuse.
- Automatic token refresh based on expires_in (with safety margin).
- Consistent TLS verification and timeouts.
- Merges headers safely; raises helpful errors.
- Convenience HTTP verb methods.
"""

from dataclasses import dataclass
import time
from typing import Any, Dict, Optional
import urllib.parse
import requests


@dataclass
class Credentials:
    username: str
    password: str


class Client:
    def __init__(
        self,
        base_url: str,
        credentials: Credentials,
        *,
        api_prefix: str = "/api/v1",
        verify_tls: bool = True,
        default_timeout: float = 30.0,
    ):
        self.base_url = base_url.rstrip("/")
        self.api_prefix = api_prefix.strip("/")
        self.credentials = credentials
        self.verify_tls = verify_tls
        self.default_timeout = default_timeout

        self._session = requests.Session()
        self._token: Optional[str] = None
        self._token_expiry_ts: float = 0.0

        self.authenticate()


    def _join(self, base: str, path: str) -> str:
        """Join base and path without losing existing path segments."""
        return urllib.parse.urljoin(base.rstrip("/") + "/", path.lstrip("/"))

    @property
    def api_base(self) -> str:
        return self._join(self.base_url, self.api_prefix)

    def endpoint(self, endpoint: str) -> str:
        return self._join(self.api_base, endpoint)

    # ---------- Auth ----------

    def authenticate(self) -> None:
        """Login and store bearer token + expiry."""
        auth_payload = {
            "username": self.credentials.username,
            "password": self.credentials.password,
        }
        url = self.endpoint("system/login")

        resp = self._session.post(
            url,
            json=auth_payload,
            headers={"Content-Type": "application/json"},
            verify=self.verify_tls,
            timeout=self.default_timeout,
        )
        if resp.status_code != 200:
            raise RuntimeError(
                f"Authentication failed ({resp.status_code}): {resp.text}"
            )

        data = resp.json()['result'] if resp.content else {}
        token = data.get("token") or data.get("access_token") or data.get("jwt")
        if not token:
            raise RuntimeError("Authentication response missing token field.")

        expires_in = data.get("expires_in", 3600)
        now = time.time()
        margin = min(60, max(10, expires_in * 0.1))
        self._token = str(token)
        self._token_expiry_ts = now + float(expires_in) - margin

    def _ensure_token(self) -> None:
        if not self._token or time.time() >= self._token_expiry_ts:
            self.authenticate()

    # ---------- Requests ----------

    def request(
        self,
        method: str,
        endpoint: str,
        *,
        auth: bool = True,
        timeout: Optional[float] = None,
        headers: Optional[Dict[str, str]] = None,
        **kwargs: Any,
    ) -> requests.Response:
        """
        Make an HTTP request to an API endpoint.
        - endpoint can be relative (e.g., "datasets") or absolute path.
        """
        url = self.endpoint(endpoint)
        req_headers: Dict[str, str] = {}
        if auth:
            self._ensure_token()
            req_headers["Authorization"] = f"Bearer {self._token}"
        if headers:
            if auth and "Authorization" in headers:
                headers = {k: v for k, v in headers.items() if k.lower() != "authorization"}
            req_headers.update(headers)

        resp = self._session.request(
            method.upper(),
            url,
            headers=req_headers,
            verify=self.verify_tls,
            timeout=self.default_timeout if timeout is None else timeout,
            **kwargs,
        )
        return resp

    def get(self, endpoint: str, **kwargs: Any) -> requests.Response:
        return self.request("GET", endpoint, **kwargs)

    def post(self, endpoint: str, **kwargs: Any) -> requests.Response:
        return self.request("POST", endpoint, **kwargs)

    def put(self, endpoint: str, **kwargs: Any) -> requests.Response:
        return self.request("PUT", endpoint, **kwargs)

    def patch(self, endpoint: str, **kwargs: Any) -> requests.Response:
        return self.request("PATCH", endpoint, **kwargs)

    def delete(self, endpoint: str, **kwargs: Any) -> requests.Response:
        return self.request("DELETE", endpoint, **kwargs)