"""qhsetech — Official Python client for the QHSE Tech Public API.

Single-file, only depends on `requests`. Works on Python 3.9+.

Get an API key at https://qhsetech.com/api-docs

Example:
    from qhsetech import QhseTechClient

    client = QhseTechClient(api_key="qhse_live_…")
    result = client.software.list(category="Incident Management", limit=10)
    print(result.pagination.total)
    for p in result.data:
        print(p.name, p.rating)
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Generic, List, Optional, TypeVar

import requests

__version__ = "1.0.0"
DEFAULT_BASE_URL = "https://nypdfolwwyubfqqljmxj.functions.supabase.co/public-api"

T = TypeVar("T")


# ---------- Errors ---------------------------------------------------------


class QhseTechApiError(Exception):
    """Raised for any non-2xx API response."""

    def __init__(
        self,
        status: int,
        code: str,
        message: str,
        request_id: str,
        retry_after_seconds: Optional[int] = None,
    ) -> None:
        super().__init__(f"[{status} {code}] {message} (request_id={request_id})")
        self.status = status
        self.code = code
        self.api_message = message
        self.request_id = request_id
        self.retry_after_seconds = retry_after_seconds


# ---------- Models ---------------------------------------------------------


@dataclass
class Pagination:
    total: int
    limit: Optional[int] = None
    offset: Optional[int] = None
    has_more: Optional[bool] = None
    next_offset: Optional[int] = None
    query: Optional[str] = None


@dataclass
class ApiResult(Generic[T]):
    data: T
    pagination: Optional[Pagination] = None


@dataclass
class SoftwareSummary:
    id: str
    name: str
    tagline: str
    description: str
    category: str
    website: str
    pricing_model: str
    rating: float
    review_count: int
    pricing_starting_at: Optional[str] = None
    badge: Optional[str] = None
    category_tags: List[str] = field(default_factory=list)
    industries: List[str] = field(default_factory=list)
    company_size: List[str] = field(default_factory=list)
    certifications: List[str] = field(default_factory=list)
    extra: dict = field(default_factory=dict)

    @classmethod
    def from_dict(cls, d: dict) -> "SoftwareSummary":
        known = {f for f in cls.__dataclass_fields__ if f != "extra"}
        kwargs = {k: d.get(k) for k in known if k in d}
        extra = {k: v for k, v in d.items() if k not in known}
        return cls(**kwargs, extra=extra)


@dataclass
class CategoryCount:
    name: str
    count: int

    @classmethod
    def from_dict(cls, d: dict) -> "CategoryCount":
        return cls(name=d["name"], count=int(d["count"]))


@dataclass
class Review:
    id: str
    software_id: str
    author_name: str
    rating: int
    review_text: str
    created_at: str
    author_role: Optional[str] = None
    author_company: Optional[str] = None
    source: Optional[str] = None

    @classmethod
    def from_dict(cls, d: dict) -> "Review":
        known = set(cls.__dataclass_fields__)
        return cls(**{k: v for k, v in d.items() if k in known})


@dataclass
class GlossaryTerm:
    term: str
    definition: str
    category: Optional[str] = None

    @classmethod
    def from_dict(cls, d: dict) -> "GlossaryTerm":
        return cls(term=d["term"], definition=d["definition"], category=d.get("category"))


# ---------- Client ---------------------------------------------------------


def _pagination_from(d: Optional[dict]) -> Optional[Pagination]:
    if not d:
        return None
    return Pagination(
        total=int(d.get("total", 0)),
        limit=d.get("limit"),
        offset=d.get("offset"),
        has_more=d.get("has_more"),
        next_offset=d.get("next_offset"),
        query=d.get("query"),
    )


class _SoftwareNamespace:
    def __init__(self, client: "QhseTechClient") -> None:
        self._c = client

    def list(
        self,
        *,
        q: Optional[str] = None,
        category: Optional[str] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
    ) -> ApiResult[List[SoftwareSummary]]:
        body = self._c._request(
            "/v1/software",
            params={"q": q, "category": category, "limit": limit, "offset": offset},
        )
        return ApiResult(
            data=[SoftwareSummary.from_dict(p) for p in body.get("data", [])],
            pagination=_pagination_from(body.get("pagination")),
        )

    def get(self, software_id: str) -> ApiResult[dict]:
        body = self._c._request(f"/v1/software/{software_id}")
        return ApiResult(data=body.get("data", {}))


class _CategoriesNamespace:
    def __init__(self, client: "QhseTechClient") -> None:
        self._c = client

    def list(self) -> ApiResult[List[CategoryCount]]:
        body = self._c._request("/v1/categories")
        return ApiResult(
            data=[CategoryCount.from_dict(c) for c in body.get("data", [])],
            pagination=_pagination_from(body.get("pagination")),
        )


class _ReviewsNamespace:
    def __init__(self, client: "QhseTechClient") -> None:
        self._c = client

    def list(self, software_id: str) -> ApiResult[List[Review]]:
        body = self._c._request(f"/v1/reviews/{software_id}")
        return ApiResult(
            data=[Review.from_dict(r) for r in body.get("data", [])],
            pagination=_pagination_from(body.get("pagination")),
        )


class _GlossaryNamespace:
    def __init__(self, client: "QhseTechClient") -> None:
        self._c = client

    def list(self, q: Optional[str] = None) -> ApiResult[List[GlossaryTerm]]:
        body = self._c._request("/v1/glossary", params={"q": q})
        return ApiResult(
            data=[GlossaryTerm.from_dict(t) for t in body.get("data", [])],
            pagination=_pagination_from(body.get("pagination")),
        )


class _SearchNamespace:
    def __init__(self, client: "QhseTechClient") -> None:
        self._c = client

    def products(
        self, *, q: str, limit: Optional[int] = None
    ) -> ApiResult[List[SoftwareSummary]]:
        body = self._c._request("/v1/search", params={"q": q, "limit": limit})
        return ApiResult(
            data=[SoftwareSummary.from_dict(p) for p in body.get("data", [])],
            pagination=_pagination_from(body.get("pagination")),
        )


class QhseTechClient:
    """Synchronous client for the QHSE Tech Public API."""

    def __init__(
        self,
        api_key: str,
        *,
        base_url: str = DEFAULT_BASE_URL,
        timeout: float = 15.0,
        session: Optional[requests.Session] = None,
        user_agent: Optional[str] = None,
    ) -> None:
        if not api_key or not api_key.startswith("qhse_"):
            raise ValueError(
                "api_key must be a `qhse_live_*` key from https://qhsetech.com/api-docs"
            )
        self.api_key = api_key
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout
        self.session = session or requests.Session()
        self._user_agent = user_agent or f"qhsetech-python/{__version__}"

        self.software = _SoftwareNamespace(self)
        self.categories = _CategoriesNamespace(self)
        self.reviews = _ReviewsNamespace(self)
        self.glossary = _GlossaryNamespace(self)
        self.search = _SearchNamespace(self)

    def _request(self, path: str, *, params: Optional[dict] = None) -> dict:
        clean_params = (
            {k: v for k, v in (params or {}).items() if v is not None and v != ""}
        )
        url = f"{self.base_url}{path}"
        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Accept": "application/json",
            "User-Agent": self._user_agent,
        }
        try:
            res = self.session.get(
                url, params=clean_params, headers=headers, timeout=self.timeout
            )
        except requests.RequestException as e:
            raise QhseTechApiError(
                status=0,
                code="network_error",
                message=str(e),
                request_id="n/a",
            ) from e

        request_id = res.headers.get("x-request-id", "n/a")
        try:
            payload: Any = res.json() if res.text else {}
        except ValueError as e:
            raise QhseTechApiError(
                status=res.status_code,
                code="invalid_response",
                message=f"Non-JSON response: {res.text[:120]}",
                request_id=request_id,
            ) from e

        if not res.ok:
            err = (payload or {}).get("error") or {}
            raise QhseTechApiError(
                status=res.status_code,
                code=err.get("code", "unknown_error"),
                message=err.get("message", f"HTTP {res.status_code}"),
                request_id=err.get("request_id", request_id),
                retry_after_seconds=err.get("retry_after_seconds"),
            )
        return payload


def create_client(api_key: str, **kwargs: Any) -> QhseTechClient:
    """Convenience factory."""
    return QhseTechClient(api_key=api_key, **kwargs)


__all__ = [
    "QhseTechClient",
    "QhseTechApiError",
    "ApiResult",
    "Pagination",
    "SoftwareSummary",
    "CategoryCount",
    "Review",
    "GlossaryTerm",
    "create_client",
    "DEFAULT_BASE_URL",
    "__version__",
]
