from __future__ import annotations

import httpx
import logging
import time
from pathlib import Path

from dattos_sdk.cache import FileCache
from dattos_sdk.exceptions import ApiError

logger = logging.getLogger("dattos_sdk")


class DattosClient:
    """HTTP client for the Dattos REST API."""

    def __init__(
        self,
        base_url: str,
        api_key: str,
        http_client: httpx.Client | None = None,
        folder_id: str = "",
        cache_dir: Path | None = None,
    ):
        self.base_url = base_url.rstrip("/")
        self._headers = {
            "accept": "application/json",
            "Authorization": f"Bearer {api_key}",
        }
        self._folder_id = folder_id
        if folder_id:
            self._headers["folder-id"] = folder_id
        self._http = http_client or httpx.Client(timeout=30, follow_redirects=False)
        self._cache = FileCache(cache_dir) if cache_dir else None

    @property
    def folder_id(self) -> str:
        """Return the configured folder ID."""
        return self._folder_id

    def _request(
        self,
        method: str,
        path: str,
        max_retries: int = 3,
        **kwargs,
    ) -> dict | list:
        url = f"{self.base_url}{path}"
        try:
            return self._do_request(method, url, path, max_retries, **kwargs)
        except httpx.ConnectError as exc:
            raise ApiError(0, f"Connection failed: {exc}", url)
        except httpx.TimeoutException as exc:
            raise ApiError(0, f"Request timed out: {exc}", url)

    def _do_request(
        self,
        method: str,
        url: str,
        path: str,
        max_retries: int,
        **kwargs,
    ) -> dict | list:
        for attempt in range(max_retries + 1):
            response = self._http.request(
                method, url, headers=self._headers, **kwargs
            )
            if response.status_code == 429 and attempt < max_retries:
                wait = min(2**attempt * 5, 30)
                logger.warning(
                    "Rate limited (429) on %s %s, retrying in %ds (%d/%d)",
                    method, path, wait, attempt + 1, max_retries,
                )
                time.sleep(wait)
                continue
            if response.status_code >= 400:
                detail = self._extract_detail(response)
                raise ApiError(
                    status_code=response.status_code,
                    detail=detail,
                    url=url,
                )
            if not response.content:
                return {}
            ct = response.headers.get("content-type", "")
            if "application/json" not in ct:
                raise ApiError(
                    status_code=response.status_code,
                    detail=f"Expected JSON but got {ct or 'unknown content-type'} (check API URL)",
                    url=url,
                )
            return response.json()
        raise ApiError(status_code=429, detail="Max retries exceeded", url=url)

    @staticmethod
    def _extract_detail(response: httpx.Response) -> str:
        try:
            body = response.json()
            detail = body.get("message", body.get("Message", str(body)))
        except (ValueError, KeyError, TypeError):
            detail = response.text or f"HTTP {response.status_code}"
        if len(detail) > 500:
            detail = detail[:500] + "..."
        return detail

    @staticmethod
    def _resource_prefix(path: str) -> str:
        """Extract resource type prefix: '/api/Connector/42' -> '/api/Connector'."""
        parts = path.split("/")
        return "/".join(parts[:3]) if len(parts) >= 3 else path

    def get(self, path: str, cache: bool = False, cache_ttl: int = 300, **kwargs) -> dict | list:
        if cache and self._cache:
            cached = self._cache.get(path, kwargs.get("params"))
            if cached is not None:
                return cached
        result = self._request("GET", path, **kwargs)
        if cache and self._cache:
            self._cache.set(path, kwargs.get("params"), result, ttl=cache_ttl)
        return result

    def post(self, path: str, **kwargs) -> dict | list:
        result = self._request("POST", path, **kwargs)
        if self._cache:
            self._cache.invalidate(self._resource_prefix(path))
        return result

    def put(self, path: str, **kwargs) -> dict | list:
        result = self._request("PUT", path, **kwargs)
        if self._cache:
            self._cache.invalidate(self._resource_prefix(path))
        return result

    def delete(self, path: str, **kwargs) -> dict | list:
        result = self._request("DELETE", path, **kwargs)
        if self._cache:
            self._cache.invalidate(self._resource_prefix(path))
        return result

    def upload(self, path: str, data: bytes, **kwargs) -> dict | list:
        """Upload raw bytes (for file content)."""
        url = f"{self.base_url}{path}"
        headers = {**self._headers, "content-type": "application/octet-stream"}
        try:
            response = self._http.request(
                "POST", url, headers=headers, content=data, **kwargs
            )
        except httpx.ConnectError as exc:
            raise ApiError(0, f"Connection failed: {exc}", url)
        except httpx.TimeoutException as exc:
            raise ApiError(0, f"Request timed out: {exc}", url)
        if response.status_code >= 400:
            detail = self._extract_detail(response)
            raise ApiError(status_code=response.status_code, detail=detail, url=url)
        if not response.content:
            return {}
        return response.json()

    def download(self, path: str, **kwargs) -> httpx.Response:
        """Download a file, returning the raw response (for binary content)."""
        url = f"{self.base_url}{path}"
        try:
            response = self._http.request(
                "GET", url, headers=self._headers, timeout=120, **kwargs
            )
        except httpx.ConnectError as exc:
            raise ApiError(0, f"Connection failed: {exc}", url)
        except httpx.TimeoutException as exc:
            raise ApiError(0, f"Request timed out: {exc}", url)
        if response.status_code >= 400:
            detail = self._extract_detail(response)
            raise ApiError(response.status_code, detail, url)
        return response
