"""DocuSign eSignature REST client for direct document verification."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any

from g_gremlin.base import (
    AuthenticationError,
    BaseIntegration,
    IntegrationError,
    RateLimitError,
    RetryConfig,
)
from g_gremlin.credentials import resolve_credentials
from g_gremlin.credentials.provider import CredentialProvider

DEFAULT_AUTH_BASE_URL = "https://account.docusign.com"
DEFAULT_RETRY_CONFIG = RetryConfig(max_attempts=4, base_delay=1.0, max_delay=10.0)


class DocuSignError(RuntimeError):
    """Raised when DocuSign API access fails."""


@dataclass(frozen=True)
class DocuSignAccountContext:
    base_uri: str
    account_id: str


@dataclass(frozen=True)
class DocuSignEnvelopeSnapshot:
    envelope: dict[str, Any]
    recipients: list[dict[str, Any]]
    form_data: list[dict[str, Any]]


def resolve_docusign_credentials(
    credential_provider: CredentialProvider | None = None,
) -> dict[str, str]:
    provider = credential_provider or CredentialProvider()
    return resolve_credentials(
        "docusign",
        required_keys=["access_token"],
        optional_keys=["account_id", "base_uri", "auth_base_url"],
        credential_provider=provider,
    )


class DocuSignClient(BaseIntegration):
    SERVICE_NAME = "docusign"

    def __init__(
        self,
        *,
        access_token: str | None = None,
        account_id: str | None = None,
        base_uri: str | None = None,
        auth_base_url: str | None = 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,
        )
        if access_token is None:
            creds = resolve_docusign_credentials(credential_provider)
            access_token = creds["access_token"]
            account_id = account_id or creds.get("account_id")
            base_uri = base_uri or creds.get("base_uri")
            auth_base_url = auth_base_url or creds.get("auth_base_url")
        self.access_token = str(access_token or "").strip()
        self.auth_base_url = _normalize_auth_base_url(auth_base_url)
        normalized_base_uri = _normalize_base_uri(base_uri)
        normalized_account_id = str(account_id or "").strip()
        self._account_context = (
            DocuSignAccountContext(
                base_uri=normalized_base_uri,
                account_id=normalized_account_id,
            )
            if normalized_base_uri and normalized_account_id
            else None
        )

    def get_envelope_snapshot(self, envelope_id: str) -> DocuSignEnvelopeSnapshot:
        normalized_id = str(envelope_id or "").strip()
        if not normalized_id:
            raise DocuSignError("DocuSign envelope ID is required.")
        envelope = self.get_envelope(normalized_id)
        recipients = self.list_recipients(normalized_id)
        form_data = self.list_form_data(normalized_id)
        return DocuSignEnvelopeSnapshot(
            envelope=envelope,
            recipients=recipients,
            form_data=form_data,
        )

    def get_envelope(self, envelope_id: str) -> dict[str, Any]:
        return self._get_json(
            f"{self._api_base()}/envelopes/{envelope_id}",
            allow_statuses=(),
        )

    def list_recipients(self, envelope_id: str) -> list[dict[str, Any]]:
        payload = self._get_json(
            f"{self._api_base()}/envelopes/{envelope_id}/recipients",
            allow_statuses=(404,),
        )
        if not payload:
            return []
        rows: list[dict[str, Any]] = []
        for key in ("signers", "inPersonSigners", "witnesses", "notaries"):
            raw_items = payload.get(key)
            if not isinstance(raw_items, list):
                continue
            rows.extend(item for item in raw_items if isinstance(item, dict))
        return rows

    def list_form_data(self, envelope_id: str) -> list[dict[str, Any]]:
        payload = self._get_json(
            f"{self._api_base()}/envelopes/{envelope_id}/form_data",
            allow_statuses=(404,),
        )
        if not payload:
            return []
        if isinstance(payload, list):
            return [item for item in payload if isinstance(item, dict)]
        if not isinstance(payload, dict):
            return []
        for key in ("formData", "form_data", "data"):
            raw_items = payload.get(key)
            if isinstance(raw_items, list):
                return [item for item in raw_items if isinstance(item, dict)]
        return []

    def _headers(self) -> dict[str, str]:
        return {
            "Authorization": f"Bearer {self.access_token}",
            "Accept": "application/json",
        }

    def _api_base(self) -> str:
        context = self._resolve_account_context()
        return f"{context.base_uri}/restapi/v2.1/accounts/{context.account_id}"

    def _resolve_account_context(self) -> DocuSignAccountContext:
        if self._account_context is not None:
            return self._account_context

        payload = self._get_json(
            f"{self.auth_base_url}/oauth/userinfo",
            allow_statuses=(),
        )
        accounts = payload.get("accounts")
        if not isinstance(accounts, list):
            raise DocuSignError("DocuSign userinfo response did not include any accounts.")

        preferred: dict[str, Any] | None = None
        fallback: dict[str, Any] | None = None
        for account in accounts:
            if not isinstance(account, dict):
                continue
            base_uri = _normalize_base_uri(account.get("base_uri") or account.get("baseUri"))
            account_id = str(account.get("account_id") or account.get("accountId") or "").strip()
            if not base_uri or not account_id:
                continue
            if fallback is None:
                fallback = account
            if _as_bool(account.get("is_default") or account.get("isDefault")):
                preferred = account
                break

        selected = preferred or fallback
        if not isinstance(selected, dict):
            raise DocuSignError("DocuSign userinfo response did not include a usable account.")

        normalized = DocuSignAccountContext(
            base_uri=_normalize_base_uri(selected.get("base_uri") or selected.get("baseUri")) or "",
            account_id=str(selected.get("account_id") or selected.get("accountId") or "").strip(),
        )
        if not normalized.base_uri or not normalized.account_id:
            raise DocuSignError("DocuSign userinfo response did not include account routing details.")
        self._account_context = normalized
        return normalized

    def _get_json(
        self,
        url: str,
        *,
        allow_statuses: tuple[int, ...],
    ) -> dict[str, Any] | list[Any]:
        try:
            response = self._get(
                url,
                headers=self._headers(),
                allow_statuses=allow_statuses,
            )
        except (AuthenticationError, RateLimitError, IntegrationError) as exc:
            raise DocuSignError(str(exc)) from exc
        if response.status_code in allow_statuses:
            return {}
        try:
            payload = response.json()
        except Exception as exc:
            raise DocuSignError(f"Failed to parse DocuSign response from {url}: {exc}") from exc
        if isinstance(payload, (dict, list)):
            return payload
        raise DocuSignError(f"Unexpected DocuSign response payload from {url}.")


def _normalize_base_uri(value: Any) -> str | None:
    normalized = str(value or "").strip().rstrip("/")
    if not normalized:
        return None
    if normalized.endswith("/restapi"):
        normalized = normalized[: -len("/restapi")]
    return normalized or None


def _normalize_auth_base_url(value: Any) -> str:
    normalized = str(value or "").strip().rstrip("/")
    if not normalized:
        return DEFAULT_AUTH_BASE_URL
    return normalized


def _as_bool(value: Any) -> bool:
    if isinstance(value, bool):
        return value
    return str(value or "").strip().lower() in {"1", "true", "yes", "y"}


__all__ = [
    "DEFAULT_AUTH_BASE_URL",
    "DocuSignAccountContext",
    "DocuSignClient",
    "DocuSignEnvelopeSnapshot",
    "DocuSignError",
    "resolve_docusign_credentials",
]
