"""
ZoomInfo enrichment client.

Performs OAuth2 client credentials flow and looks up companies by domain.
Tokens are cached in ~/.g_gremlin/config.json (keys: zoominfo_access_token,
zoominfo_refresh_token, zoominfo_token_expires_at).
"""

from __future__ import annotations

import json
import time
from pathlib import Path
from typing import Any, Dict, Optional

import click

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

CONFIG_PATH = Path.home() / ".g_gremlin" / "config.json"
TOKEN_URL = "https://api.zoominfo.com/auth/token"
LOOKUP_URL = "https://api.zoominfo.com/lookup/company"
DEFAULT_RETRY_CONFIG = RetryConfig(max_attempts=4, base_delay=1.0, max_delay=12.0)


class ZoomInfoError(RuntimeError):
    """Raised when ZoomInfo operations fail."""


class ZoomInfoAuthError(ZoomInfoError):
    """Raised when access tokens are invalid/expired."""


def _load_config() -> Dict[str, Any]:
    if not CONFIG_PATH.exists():
        return {}
    try:
        return json.loads(CONFIG_PATH.read_text(encoding="utf-8"))
    except Exception:
        return {}


def _save_config(config: Dict[str, Any]) -> None:
    CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
    CONFIG_PATH.write_text(json.dumps(config, indent=2), encoding="utf-8")


class ZoomInfoClient(BaseIntegration):
    SERVICE_NAME = "zoominfo"

    def __init__(
        self,
        client_id: Optional[str] = None,
        client_secret: Optional[str] = None,
        credential_provider: CredentialProvider | None = None,
        retry_config: RetryConfig | None = None,
    ) -> None:
        super().__init__(credential_provider=credential_provider, default_retry_config=retry_config or DEFAULT_RETRY_CONFIG)
        self.config = _load_config()
        creds = self._resolve_creds(client_id, client_secret, credential_provider)
        self._client_id = creds["client_id"]
        self._client_secret = creds["client_secret"]

    def _resolve_creds(
        self,
        client_id: Optional[str],
        client_secret: Optional[str],
        credential_provider: CredentialProvider | None,
    ) -> Dict[str, str]:
        if client_id and client_secret:
            return {"client_id": client_id, "client_secret": client_secret}
        try:
            creds = resolve_credentials(
                "zoominfo",
                required_keys=["client_id", "client_secret"],
                optional_keys=["access_token", "refresh_token", "token_expires_at"],
                credential_provider=credential_provider,
            )
            # Merge into config for caching
            self.config.setdefault("zoominfo_access_token", creds.get("access_token"))
            self.config.setdefault("zoominfo_refresh_token", creds.get("refresh_token"))
            if creds.get("token_expires_at"):
                self.config.setdefault("zoominfo_token_expires_at", creds.get("token_expires_at"))
            return {"client_id": creds["client_id"], "client_secret": creds["client_secret"]}
        except SecretNotFoundError:
            # Fallback to legacy config file fields
            cid = (self.config.get("zoominfo_client_id") or "").strip()
            csecret = (self.config.get("zoominfo_client_secret") or "").strip()
            if cid and csecret:
                return {"client_id": cid, "client_secret": csecret}
            raise click.ClickException(
                "ZoomInfo client credentials not configured. "
                "Set env vars G_GREMLIN_ZOOMINFO_CLIENT_ID / G_GREMLIN_ZOOMINFO_CLIENT_SECRET "
                "or run g-gremlin auth set zoominfo."
            )

    # ------------------------------------------------------------------ #
    # Token handling
    # ------------------------------------------------------------------ #
    def _token_valid(self) -> bool:
        expires_at = self.config.get("zoominfo_token_expires_at")
        access_token = self.config.get("zoominfo_access_token")
        if not access_token or not expires_at:
            return False
        try:
            return time.time() < float(expires_at) - 30
        except Exception:
            return False

    def _store_tokens(self, tokens: Dict[str, Any]) -> None:
        access_token = tokens.get("access_token")
        refresh_token = tokens.get("refresh_token") or self.config.get("zoominfo_refresh_token")
        expires_in = tokens.get("expires_in", 3600)

        self.config["zoominfo_access_token"] = access_token
        self.config["zoominfo_refresh_token"] = refresh_token
        self.config["zoominfo_token_expires_at"] = time.time() + float(expires_in)
        _save_config(self.config)

    def _request_tokens(self, refresh_token: Optional[str]) -> Dict[str, Any]:
        data: Dict[str, Any]
        if refresh_token:
            data = {"grant_type": "refresh_token", "refresh_token": refresh_token}
        else:
            data = {"grant_type": "client_credentials"}

        try:
            resp = self._post(
                TOKEN_URL,
                auth=(self._client_id, self._client_secret),
                json=data,
                allow_statuses=(400,),
                timeout=20,
            )
        except AuthenticationError as exc:
            raise ZoomInfoAuthError("ZoomInfo credentials rejected.") from exc
        except (RateLimitError, IntegrationError, TransientError) as exc:
            raise ZoomInfoError(str(exc)) from exc

        try:
            return resp.json()
        except Exception as exc:  # pragma: no cover - defensive
            raise ZoomInfoError(f"Invalid token response: {exc}") from exc

    def _get_access_token(self) -> str:
        if self._token_valid():
            return self.config["zoominfo_access_token"]

        refresh_token = self.config.get("zoominfo_refresh_token")
        tokens = self._request_tokens(refresh_token if refresh_token else None)
        self._store_tokens(tokens)
        access_token = tokens.get("access_token")
        if not access_token:
            raise ZoomInfoError("ZoomInfo token response missing access_token.")
        return access_token

    # ------------------------------------------------------------------ #
    # Lookup
    # ------------------------------------------------------------------ #
    def _lookup_company(self, token: str, domain: str) -> Dict[str, Any]:
        headers = {"Authorization": f"Bearer {token}"}
        params = {"website": domain}
        try:
            resp = self._get(
                LOOKUP_URL,
                headers=headers,
                params=params,
                allow_statuses=(404,),
            )
        except RateLimitError as exc:
            raise ZoomInfoError(str(exc)) from exc
        except AuthenticationError as exc:
            raise ZoomInfoAuthError(str(exc)) from exc
        except (IntegrationError, TransientError) as exc:
            raise ZoomInfoError(str(exc)) from exc

        if resp.status_code == 404:
            return {}
        try:
            return resp.json()
        except Exception as exc:  # pragma: no cover - defensive
            raise ZoomInfoError(f"Invalid ZoomInfo response: {exc}") from exc

    def enrich_company(self, domain: str, fields: Optional[list[str]] = None) -> Dict[str, Any]:
        domain_clean = (domain or "").strip()
        if not domain_clean:
            return {}

        token = self._get_access_token()

        try:
            company = self._lookup_company(token, domain_clean)
        except ZoomInfoAuthError:
            # Refresh tokens and retry once
            tokens = self._request_tokens(self.config.get("zoominfo_refresh_token"))
            self._store_tokens(tokens)
            token = tokens.get("access_token", "")
            company = self._lookup_company(token, domain_clean)

        normalized = _extract_fields(company)
        if fields:
            return {k: normalized.get(k) for k in fields if k in normalized}
        return normalized


def _extract_fields(company: Dict[str, Any]) -> Dict[str, Any]:
    if not company:
        return {}
    return {
        "name": company.get("name") or company.get("companyName") or company.get("company_name"),
        "domain": company.get("website") or company.get("domain"),
        "revenue": company.get("revenue") or company.get("annual_revenue"),
        "employee_count": company.get("employeeCount") or company.get("employee_count"),
        "industry": company.get("industry"),
        "headquarters": company.get("headquarters") or company.get("headquartersAddress"),
    }


def enrich_company(domain: str, fields: Optional[list[str]] = None) -> Dict[str, Any]:
    """
    Enrich a company by domain using ZoomInfo.
    """
    client = ZoomInfoClient()
    try:
        return client.enrich_company(domain, fields)
    except (ZoomInfoError, ZoomInfoAuthError) as exc:
        raise click.ClickException(str(exc)) from exc


__all__ = [
    "ZoomInfoError",
    "ZoomInfoAuthError",
    "ZoomInfoClient",
    "enrich_company",
]
