"""Dataverse Web API client (OData v4)."""

from __future__ import annotations

from typing import Any, Dict, Iterable

from g_gremlin.base import BaseIntegration, IntegrationError

from .dynamics_auth import DataverseAuthenticator


class DataverseClient(BaseIntegration):
    SERVICE_NAME = "dynamics"

    ENTITY_SET_NAMES = {
        "account": "accounts",
        "contact": "contacts",
        "lead": "leads",
        "opportunity": "opportunities",
        "task": "tasks",
        "contract": "contracts",
    }

    def __init__(
        self,
        environment_url: str,
        authenticator: DataverseAuthenticator,
        *,
        credential_provider=None,
        retry_config=None,
    ) -> None:
        super().__init__(credential_provider=credential_provider, default_retry_config=retry_config)
        self.environment_url = environment_url.rstrip("/")
        self.base_url = f"{self.environment_url}/api/data/v9.2"
        self.authenticator = authenticator
        self._entity_set_cache: Dict[str, str] = {}

    def query(
        self,
        entity: str,
        *,
        select: Iterable[str] | None = None,
        filter_expr: str | None = None,
        orderby: str | None = None,
        top: int | None = None,
        count: bool = False,
    ) -> Dict[str, Any]:
        entity_set = self.resolve_entity_set(entity)
        params: Dict[str, Any] = {}
        if select:
            params["$select"] = ",".join(select)
        if filter_expr:
            params["$filter"] = filter_expr
        if orderby:
            params["$orderby"] = orderby
        if top is None:
            top = 5000
        params["$top"] = int(top)
        if count:
            params["$count"] = "true"

        prefer = (
            'odata.include-annotations="Microsoft.Dynamics.CRM.totalrecordcount,'
            'Microsoft.Dynamics.CRM.totalrecordcountlimitexceeded"'
            if count
            else 'odata.include-annotations=*'
        )
        resp = self.get(entity_set, params=params, prefer=prefer)
        try:
            return resp.json()
        except Exception as exc:  # pragma: no cover - defensive
            raise IntegrationError(f"Invalid Dataverse response: {exc}") from exc

    def get_entity_metadata(self, logical_name: str) -> Dict[str, Any]:
        params = {"$select": "LogicalName,AttributeType,DisplayName"}
        resp = self.get(f"EntityDefinitions(LogicalName='{logical_name}')/Attributes", params=params)
        try:
            return resp.json()
        except Exception as exc:  # pragma: no cover - defensive
            raise IntegrationError(f"Invalid Dataverse metadata response: {exc}") from exc

    def resolve_entity_set(self, logical_name: str) -> str:
        key = str(logical_name).lower()
        if key in self.ENTITY_SET_NAMES:
            return self.ENTITY_SET_NAMES[key]
        if key in self._entity_set_cache:
            return self._entity_set_cache[key]

        resp = self.get(
            f"EntityDefinitions(LogicalName='{logical_name}')",
            params={"$select": "EntitySetName"},
        )
        try:
            payload = resp.json()
        except Exception as exc:  # pragma: no cover - defensive
            raise IntegrationError(f"Invalid Dataverse entity set response: {exc}") from exc
        entity_set = payload.get("EntitySetName")
        if not entity_set:
            raise IntegrationError(f"Dataverse entity set not found for {logical_name}.")
        self._entity_set_cache[key] = entity_set
        return entity_set

    def build_headers(self, *, prefer: str | None = None) -> Dict[str, str]:
        token = self.authenticator.get_token()
        headers = {
            "Authorization": f"Bearer {token}",
            "OData-Version": "4.0",
        }
        if prefer:
            headers["Prefer"] = prefer
        return headers

    def get(
        self,
        path: str,
        *,
        params: Dict[str, Any] | None = None,
        headers: Dict[str, str] | None = None,
        prefer: str | None = None,
        allow_statuses: tuple[int, ...] = (),
    ):
        merged_headers = self.build_headers(prefer=prefer)
        if headers:
            merged_headers.update(headers)
        url = path if str(path).startswith("http") else f"{self.base_url}/{str(path).lstrip('/')}"
        return self._get(url, params=params, headers=merged_headers, allow_statuses=allow_statuses)

    def post(
        self,
        path: str,
        *,
        json_payload: Dict[str, Any] | None = None,
        headers: Dict[str, str] | None = None,
        allow_statuses: tuple[int, ...] = (),
    ):
        merged_headers = self.build_headers()
        if headers:
            merged_headers.update(headers)
        url = path if str(path).startswith("http") else f"{self.base_url}/{str(path).lstrip('/')}"
        return self._post(url, json=json_payload, headers=merged_headers, allow_statuses=allow_statuses)

    def put(
        self,
        path: str,
        *,
        json_payload: Dict[str, Any] | None = None,
        headers: Dict[str, str] | None = None,
        allow_statuses: tuple[int, ...] = (),
    ):
        merged_headers = self.build_headers()
        if headers:
            merged_headers.update(headers)
        url = path if str(path).startswith("http") else f"{self.base_url}/{str(path).lstrip('/')}"
        return self._put(url, json=json_payload, headers=merged_headers, allow_statuses=allow_statuses)

    def patch(
        self,
        path: str,
        *,
        json_payload: Dict[str, Any] | None = None,
        headers: Dict[str, str] | None = None,
        allow_statuses: tuple[int, ...] = (),
    ):
        merged_headers = self.build_headers()
        if headers:
            merged_headers.update(headers)
        url = path if str(path).startswith("http") else f"{self.base_url}/{str(path).lstrip('/')}"
        return self._patch(url, json=json_payload, headers=merged_headers, allow_statuses=allow_statuses)

    def delete(
        self,
        path: str,
        *,
        headers: Dict[str, str] | None = None,
        allow_statuses: tuple[int, ...] = (),
    ):
        merged_headers = self.build_headers()
        if headers:
            merged_headers.update(headers)
        url = path if str(path).startswith("http") else f"{self.base_url}/{str(path).lstrip('/')}"
        return self._delete(url, headers=merged_headers, allow_statuses=allow_statuses)

    # Backward-compatible aliases for older callers.
    def _resolve_entity_set(self, logical_name: str) -> str:
        return self.resolve_entity_set(logical_name)

    def _headers(self, *, prefer: str | None = None) -> Dict[str, str]:
        return self.build_headers(prefer=prefer)


__all__ = ["DataverseClient"]
