"""
Zendesk REST API helper utilities for g-gremlin.
"""

from __future__ import annotations

import json
from dataclasses import asdict, dataclass
from pathlib import Path
from typing import Any, Dict, Iterable, List, Optional, Sequence


from g_gremlin.base import AuthenticationError, BaseIntegration, IntegrationError, RateLimitError, RetryConfig
from g_gremlin.credentials import resolve_credentials
from g_gremlin.credentials.provider import CredentialProvider, SecretNotFoundError
from g_gremlin.paths import gremlin_path

ZENDESK_CONFIG_PATH = gremlin_path("zendesk.json")


class ZendeskError(RuntimeError):
    """Raised when Zendesk operations fail."""


@dataclass
class ZendeskConfig:
    subdomain: str
    email: str
    api_token: str


def save_zendesk_config(config: ZendeskConfig) -> None:
    """
    Persist Zendesk configuration to disk.
    """
    ZENDESK_CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
    ZENDESK_CONFIG_PATH.write_text(json.dumps(asdict(config), indent=2), encoding="utf-8")


def load_zendesk_config() -> ZendeskConfig:
    """
    Load Zendesk configuration from disk.
    """
    if not ZENDESK_CONFIG_PATH.exists():
        raise ZendeskError("Zendesk is not configured. Run 'g-gremlin zendesk connect' first.")
    try:
        data = json.loads(ZENDESK_CONFIG_PATH.read_text(encoding="utf-8"))
    except json.JSONDecodeError as exc:
        raise ZendeskError(f"Invalid Zendesk config at {ZENDESK_CONFIG_PATH}: {exc}") from exc

    subdomain = (data.get("subdomain") or "").strip()
    email = (data.get("email") or "").strip()
    api_token = (data.get("api_token") or "").strip()
    if not subdomain or not email or not api_token:
        raise ZendeskError(
            f"Incomplete Zendesk config at {ZENDESK_CONFIG_PATH}. "
            "Re-run 'g-gremlin zendesk connect'."
        )
    return ZendeskConfig(subdomain=subdomain, email=email, api_token=api_token)


def extract_zendesk_id(value: str) -> str:
    """
    Extract a numeric Zendesk ID from a URL or return the trimmed raw ID.
    Examples:
      - 'https://foo.zendesk.com/agent/organizations/12345' -> '12345'
      - ' 12345 ' -> '12345'
      - '' -> ''
    """
    raw = (value or "").strip()
    if not raw:
        return ""
    lower = raw.lower()
    if "zendesk.com" not in lower:
        return raw
    trimmed = raw.rstrip("/")
    parts = trimmed.split("/")
    return parts[-1] if parts else ""


class ZendeskClient(BaseIntegration):
    """Zendesk client with pagination and retry handling."""

    SERVICE_NAME = "zendesk"

    def __init__(
        self,
        subdomain: Optional[str] = None,
        email: Optional[str] = None,
        api_token: Optional[str] = None,
        credential_provider: CredentialProvider | None = None,
        retry_config: RetryConfig | None = None,
    ) -> None:
        retry_cfg = retry_config or RetryConfig(max_attempts=4, base_delay=1.2, max_delay=15.0)
        super().__init__(credential_provider=credential_provider, default_retry_config=retry_cfg)

        creds = self._load_credentials(subdomain, email, api_token)
        self.subdomain = creds["subdomain"]
        self.email = creds["email"]
        self.api_token = creds["api_token"]
        self.base_url = f"https://{self.subdomain}.zendesk.com/api/v2"

    def _load_credentials(self, subdomain: Optional[str], email: Optional[str], api_token: Optional[str]) -> Dict[str, str]:
        if subdomain and email and api_token:
            return {"subdomain": subdomain, "email": email, "api_token": api_token}

        def _legacy_loader() -> Dict[str, str]:
            cfg = load_zendesk_config()
            return {"subdomain": cfg.subdomain, "email": cfg.email, "api_token": cfg.api_token}

        try:
            creds = resolve_credentials(
                "zendesk",
                required_keys=["subdomain", "email", "api_token"],
                credential_provider=self.credential_provider,
                legacy_loader=_legacy_loader,
            )
            return creds
        except SecretNotFoundError as exc:
            raise ZendeskError(
                "Zendesk not configured. Set env vars G_GREMLIN_ZENDESK_SUBDOMAIN, "
                "G_GREMLIN_ZENDESK_EMAIL, G_GREMLIN_ZENDESK_API_TOKEN or run g-gremlin auth set zendesk."
            ) from exc

    # ------------------------------------------------------------------ #
    # Request helpers
    # ------------------------------------------------------------------ #
    def _auth_tuple(self) -> tuple[str, str]:
        return (f"{self.email}/token", self.api_token)

    def get_json(self, path: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
        url = path if path.lower().startswith("http") else f"{self.base_url}/{path.lstrip('/')}"
        try:
            resp = self._get(
                url,
                params=params or {},
                headers={"Accept": "application/json"},
                timeout=30,
                auth=self._auth_tuple(),
            )
        except (IntegrationError, AuthenticationError, RateLimitError) as exc:
            raise ZendeskError(str(exc)) from exc
        try:
            return resp.json()
        except ValueError as exc:
            raise ZendeskError(f"Failed to parse JSON from Zendesk response: {exc}") from exc

    # ------------------------------------------------------------------ #
    # Pagination and list helpers
    # ------------------------------------------------------------------ #
    def paginate(
        self,
        path: str,
        params: Optional[Dict[str, Any]] = None,
        result_keys: Optional[Sequence[str]] = None,
    ) -> Iterable[Dict[str, Any]]:
        next_url: Optional[str] = path if path.lower().startswith("http") else f"{self.base_url}/{path.lstrip('/')}"
        query_params = params or {}
        first = True
        keys = list(result_keys) if result_keys else ["tickets", "users", "results"]

        while next_url:
            data = self.get_json(next_url, params=query_params if first else None)
            first = False
            # Zendesk returns next_page or links.next
            next_url = data.get("next_page") or data.get("links", {}).get("next")
            results: List[Dict[str, Any]] = []
            for key in keys:
                payload = data.get(key)
                if isinstance(payload, list):
                    results = payload
                    break
            for item in results:
                yield item

    def list_tickets(self, params: Optional[Dict[str, Any]] = None) -> List[Dict[str, Any]]:
        return list(self.paginate("/tickets.json", params=params))

    def list_users(self, params: Optional[Dict[str, Any]] = None) -> List[Dict[str, Any]]:
        return list(self.paginate("/users.json", params=params))

    def list_organizations(self, params: Optional[Dict[str, Any]] = None) -> List[Dict[str, Any]]:
        return list(self.paginate("/organizations.json", params=params, result_keys=["organizations"]))


def run_zendesk_get(
    path: str, params: Optional[Dict[str, Any]] = None, config: Optional[ZendeskConfig] = None
) -> Dict[str, Any]:
    """
    Perform a GET to the Zendesk API and return parsed JSON as a dict.

    - Base URL: https://{subdomain}.zendesk.com/api/v2/{path}
    - Auth: basic (user: email/token, password: api_token)
    - Raises ZendeskError on network errors, non-2xx responses, or invalid JSON.
    """
    if not path:
        raise ZendeskError("Path is required for Zendesk API call.")
    client = ZendeskClient(
        subdomain=config.subdomain if config else None,
        email=config.email if config else None,
        api_token=config.api_token if config else None,
    )
    data = client.get_json(path, params=params)
    if not isinstance(data, dict):
        raise ZendeskError("Unexpected Zendesk response shape (expected JSON object).")
    return data


def paginate_zendesk(
    path: str,
    params: Optional[Dict[str, Any]] = None,
    config: Optional[ZendeskConfig] = None,
    result_keys: Optional[Sequence[str]] = None,
) -> Iterable[Dict[str, Any]]:
    client = ZendeskClient(
        subdomain=config.subdomain if config else None,
        email=config.email if config else None,
        api_token=config.api_token if config else None,
    )
    return client.paginate(path, params=params, result_keys=result_keys)


def list_tickets(params: Optional[Dict[str, Any]] = None, config: Optional[ZendeskConfig] = None) -> List[Dict[str, Any]]:
    client = ZendeskClient(
        subdomain=config.subdomain if config else None,
        email=config.email if config else None,
        api_token=config.api_token if config else None,
    )
    return client.list_tickets(params=params)


def list_users(params: Optional[Dict[str, Any]] = None, config: Optional[ZendeskConfig] = None) -> List[Dict[str, Any]]:
    client = ZendeskClient(
        subdomain=config.subdomain if config else None,
        email=config.email if config else None,
        api_token=config.api_token if config else None,
    )
    return client.list_users(params=params)


def list_organizations(params: Optional[Dict[str, Any]] = None, config: Optional[ZendeskConfig] = None) -> List[Dict[str, Any]]:
    client = ZendeskClient(
        subdomain=config.subdomain if config else None,
        email=config.email if config else None,
        api_token=config.api_token if config else None,
    )
    return client.list_organizations(params=params)
