from __future__ import annotations

from typing import Any

import httpx

from .auth import EasybillAuth
from .models import Customer, Document, PagedResult, WebhookSubscription


class EasybillAPIError(RuntimeError):
    def __init__(self, status_code: int, message: str, *, response_text: str = "") -> None:
        super().__init__(f"easybill API error {status_code}: {message}")
        self.status_code = status_code
        self.response_text = response_text


class _BaseEasybillClient:
    def __init__(
        self,
        api_key: str,
        *,
        email: str | None = None,
        use_basic_auth: bool = False,
        base_url: str = "https://api.easybill.de",
        api_prefix: str = "/rest/v1",
        timeout: float = 30.0,
        default_limit: int = 100,
        transport: httpx.BaseTransport | httpx.AsyncBaseTransport | None = None,
    ) -> None:
        self.base_url = base_url.rstrip("/")
        self.api_prefix = api_prefix.rstrip("/")
        self.timeout = timeout
        self.default_limit = default_limit
        self.transport = transport
        self.auth = EasybillAuth(api_key, email=email, use_basic_auth=use_basic_auth)
        self.default_headers = {
            "Accept": "application/json",
            "Content-Type": "application/json",
        }

    @staticmethod
    def _check_response(response: httpx.Response) -> httpx.Response:
        if response.is_error:
            message = response.reason_phrase or "request failed"
            raise EasybillAPIError(response.status_code, message, response_text=response.text)
        return response

    def _build_path(self, path: str) -> str:
        normalized = path if path.startswith("/") else f"/{path}"
        if isinstance(self.transport, httpx.MockTransport):
            return normalized
        return f"{self.api_prefix}{normalized}" if self.api_prefix else normalized

    @staticmethod
    def _clean_params(params: dict[str, Any] | None) -> dict[str, Any] | None:
        if not params:
            return None

        cleaned: dict[str, Any] = {}
        for key, value in params.items():
            if value is None:
                continue
            if isinstance(value, (list, tuple, set)):
                cleaned[key] = ",".join(str(item) for item in value)
            else:
                cleaned[key] = value
        return cleaned or None

    @staticmethod
    def _parse_paged_result(data: dict[str, Any], item_model: type[Customer] | type[Document] | type[WebhookSubscription]) -> PagedResult:
        items = [item_model.model_validate(item) for item in data.get("items", [])]
        return PagedResult(
            page=data.get("page", 1),
            pages=data.get("pages", 1),
            limit=data.get("limit", len(items) or 100),
            total=data.get("total", len(items)),
            items=items,
        )


class EasybillClient(_BaseEasybillClient):
    def __init__(self, api_key: str, **kwargs: Any) -> None:
        super().__init__(api_key, **kwargs)
        self._client = httpx.Client(
            base_url=self.base_url,
            timeout=self.timeout,
            headers=self.default_headers,
            auth=self.auth,
            transport=self.transport,
        )

    def close(self) -> None:
        self._client.close()

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

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

    def request(self, method: str, path: str, **kwargs: Any) -> httpx.Response:
        response = self._client.request(method, self._build_path(path), **kwargs)
        return self._check_response(response)

    def get_json(self, path: str, **kwargs: Any) -> Any:
        return self.request("GET", path, **kwargs).json()

    def post_json(self, path: str, **kwargs: Any) -> Any:
        return self.request("POST", path, **kwargs).json()

    def get_customer(self, customer_id: int) -> Customer:
        return Customer.model_validate(self.get_json(f"/customers/{customer_id}"))

    def list_customers(self, *, page: int = 1, limit: int | None = None, **filters: Any) -> PagedResult:
        params = self._clean_params({"page": page, "limit": limit or self.default_limit, **filters})
        data = self.get_json("/customers", params=params)
        return self._parse_paged_result(data, Customer)

    def get_document(self, document_id: int) -> Document:
        return Document.model_validate(self.get_json(f"/documents/{document_id}"))

    def list_documents(self, *, page: int = 1, limit: int | None = None, **filters: Any) -> PagedResult:
        params = self._clean_params({"page": page, "limit": limit or self.default_limit, **filters})
        data = self.get_json("/documents", params=params)
        return self._parse_paged_result(data, Document)

    def list_webhooks(self, *, page: int = 1, limit: int | None = None) -> PagedResult:
        params = self._clean_params({"page": page, "limit": limit or self.default_limit})
        data = self.get_json("/webhooks", params=params)
        return self._parse_paged_result(data, WebhookSubscription)

    def create_webhook(
        self,
        *,
        url: str,
        events: list[str],
        secret: str,
        description: str,
        content_type: str = "json",
        is_active: bool = True,
    ) -> WebhookSubscription:
        payload = {
            "url": url,
            "events": events,
            "secret": secret,
            "description": description,
            "content_type": content_type,
            "is_active": is_active,
        }
        data = self.post_json("/webhooks", json=payload)
        return WebhookSubscription.model_validate(data)


class AsyncEasybillClient(_BaseEasybillClient):
    def __init__(self, api_key: str, **kwargs: Any) -> None:
        super().__init__(api_key, **kwargs)
        self._client = httpx.AsyncClient(
            base_url=self.base_url,
            timeout=self.timeout,
            headers=self.default_headers,
            auth=self.auth,
            transport=self.transport,
        )

    async def aclose(self) -> None:
        await self._client.aclose()

    async def __aenter__(self) -> "AsyncEasybillClient":
        return self

    async def __aexit__(self, *_: Any) -> None:
        await self.aclose()

    async def request(self, method: str, path: str, **kwargs: Any) -> httpx.Response:
        response = await self._client.request(method, self._build_path(path), **kwargs)
        return self._check_response(response)

    async def get_json(self, path: str, **kwargs: Any) -> Any:
        response = await self.request("GET", path, **kwargs)
        return response.json()

    async def post_json(self, path: str, **kwargs: Any) -> Any:
        response = await self.request("POST", path, **kwargs)
        return response.json()

    async def get_customer(self, customer_id: int) -> Customer:
        return Customer.model_validate(await self.get_json(f"/customers/{customer_id}"))

    async def list_customers(self, *, page: int = 1, limit: int | None = None, **filters: Any) -> PagedResult:
        params = self._clean_params({"page": page, "limit": limit or self.default_limit, **filters})
        data = await self.get_json("/customers", params=params)
        return self._parse_paged_result(data, Customer)

    async def get_document(self, document_id: int) -> Document:
        return Document.model_validate(await self.get_json(f"/documents/{document_id}"))

    async def list_documents(self, *, page: int = 1, limit: int | None = None, **filters: Any) -> PagedResult:
        params = self._clean_params({"page": page, "limit": limit or self.default_limit, **filters})
        data = await self.get_json("/documents", params=params)
        return self._parse_paged_result(data, Document)

    async def list_webhooks(self, *, page: int = 1, limit: int | None = None) -> PagedResult:
        params = self._clean_params({"page": page, "limit": limit or self.default_limit})
        data = await self.get_json("/webhooks", params=params)
        return self._parse_paged_result(data, WebhookSubscription)

    async def create_webhook(
        self,
        *,
        url: str,
        events: list[str],
        secret: str,
        description: str,
        content_type: str = "json",
        is_active: bool = True,
    ) -> WebhookSubscription:
        payload = {
            "url": url,
            "events": events,
            "secret": secret,
            "description": description,
            "content_type": content_type,
            "is_active": is_active,
        }
        data = await self.post_json("/webhooks", json=payload)
        return WebhookSubscription.model_validate(data)
