"""
HeyReach activation client for g-gremlin.

Implements a thin ActivationProvider-style client with conservative rate
limits and idempotency helpers for enrolling prospects.
"""

from __future__ import annotations

import hashlib
import time
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional, Protocol

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


HEYREACH_BASE_URL = "https://api.heyreach.io/api/public"
DEFAULT_RETRY_CONFIG = RetryConfig(max_attempts=4, base_delay=1.0, max_delay=12.0)


@dataclass
class RateLimitConfig:
    """Simple rate limit configuration for HeyReach."""

    requests_per_second: int = 2
    burst_limit: int = 5
    daily_limit: int = 100
    retry_after_429: int = 60
    batch_size: int = 10
    delay_between_batches_ms: int = 500


HEYREACH_RATE_LIMIT = RateLimitConfig()


@dataclass
class HeyReachProspect:
    """Contact to enroll in HeyReach campaign."""

    email: str
    linkedin_url: Optional[str] = None
    sender_account_id: Optional[str] = None
    first_name: Optional[str] = None
    last_name: Optional[str] = None
    company: Optional[str] = None
    title: Optional[str] = None
    custom_vars: Dict[str, str] = field(default_factory=dict)


@dataclass
class HeyReachCampaign:
    """Campaign/sequence definition."""

    id: str
    name: str
    status: str
    metadata: Dict[str, Any] = field(default_factory=dict)


@dataclass
class HeyReachLinkedInAccount:
    """LinkedIn sender account in HeyReach."""

    id: str
    email: Optional[str] = None
    first_name: Optional[str] = None
    last_name: Optional[str] = None
    is_active: Optional[bool] = None
    auth_is_valid: Optional[bool] = None
    is_valid_navigator: Optional[bool] = None
    is_valid_recruiter: Optional[bool] = None
    active_campaigns: Optional[int] = None
    metadata: Dict[str, Any] = field(default_factory=dict)

    def display_name(self) -> str:
        parts = [p for p in [self.first_name, self.last_name] if p]
        if parts:
            return " ".join(parts)
        return self.email or ""

@dataclass
class EnrollmentResult:
    """Per-prospect enrollment outcome."""

    prospect_email: str
    status: str
    campaign_id: str
    provider_prospect_id: Optional[str] = None
    error: Optional[str] = None

    def to_dict(self) -> Dict[str, Any]:
        data = {
            "prospect_email": self.prospect_email,
            "status": self.status,
            "campaign_id": self.campaign_id,
        }
        if self.provider_prospect_id:
            data["provider_prospect_id"] = self.provider_prospect_id
        if self.error:
            data["error"] = self.error
        return data


@dataclass
class ActivationLogEntry:
    idempotency_key: str
    correlation_id: str
    provider: str
    campaign_id: str
    prospect_email: str
    status: str
    provider_prospect_id: Optional[str] = None
    error_message: Optional[str] = None
    created_at: float = field(default_factory=lambda: time.time())


class HeyReachError(RuntimeError):
    """Generic HeyReach client error."""


class HeyReachAuthError(HeyReachError):
    """Authentication failed."""


class ActivationProvider(Protocol):
    """Interface for outbound activation platforms."""

    @property
    def name(self) -> str:
        ...

    @property
    def rate_limit(self) -> RateLimitConfig:
        ...

    def test_connection(self) -> Dict[str, Any]:
        ...

    def list_campaigns(self, status: Optional[str] = None) -> List[HeyReachCampaign]:
        ...

    def enroll_prospects(
        self,
        prospects: List[HeyReachProspect],
        campaign_id: str,
        idempotency_keys: Optional[List[str]] = None,
    ) -> BatchOutcome:
        ...

    def get_prospect_status(self, campaign_id: str, email: str) -> Optional[Dict[str, Any]]:
        ...


class HeyReachClient(BaseIntegration, ActivationProvider):
    """Lightweight HeyReach API client."""

    SERVICE_NAME = "heyreach"

    def __init__(
        self,
        api_key: Optional[str] = None,
        *,
        workspace_id: Optional[str] = None,
        session: Optional[Any] = None,
        rate_limit: RateLimitConfig = HEYREACH_RATE_LIMIT,
        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 session is not None:
            self._session = session
        self.api_key, self.workspace_id = self._load_creds(api_key, workspace_id)
        self._rate_limit = rate_limit

    @property
    def name(self) -> str:  # pragma: no cover - trivial
        return "heyreach"

    @property
    def rate_limit(self) -> RateLimitConfig:
        return self._rate_limit

    def _headers(self) -> Dict[str, str]:
        headers = {
            "X-API-Key": self.api_key,
            "Content-Type": "application/json",
        }
        if self.workspace_id:
            headers["X-Workspace-Id"] = self.workspace_id
        return headers

    def _load_creds(
        self,
        api_key: Optional[str],
        workspace_id: Optional[str],
    ) -> tuple[str, Optional[str]]:
        if api_key:
            if workspace_id is None:
                creds = resolve_credentials(
                    "heyreach",
                    required_keys=[],
                    optional_keys=["workspace_id"],
                    credential_provider=self.credential_provider,
                )
                workspace_id = creds.get("workspace_id")
            return api_key, workspace_id
        try:
            creds = resolve_credentials(
                "heyreach",
                required_keys=["api_key"],
                optional_keys=["workspace_id"],
                credential_provider=self.credential_provider,
            )
            return creds["api_key"], workspace_id or creds.get("workspace_id")
        except SecretNotFoundError as exc:
            raise HeyReachAuthError(
                "HeyReach not configured. Set G_GREMLIN_HEYREACH_API_KEY or run g-gremlin auth set heyreach."
            ) from exc

    def _heyreach_request(
        self,
        method: str,
        path: str,
        json: Optional[Dict[str, Any]] = None,
        allow_statuses: tuple[int, ...] = (),
    ):
        url = f"{HEYREACH_BASE_URL}{path}"
        try:
            return self._request(
                method,
                url,
                headers=self._headers(),
                json=json,
                timeout=30,
                allow_statuses=allow_statuses,
            )
        except AuthenticationError as exc:
            raise HeyReachAuthError(str(exc)) from exc
        except IntegrationError as exc:
            raise HeyReachError(str(exc)) from exc

    def test_connection(self) -> Dict[str, Any]:
        resp = self._heyreach_request("GET", "/auth/CheckApiKey")
        data = resp.json() if resp.content else {}
        return {"status": "ok", "profile": data.get("profile"), "workspace": data.get("workspace")}

    def list_campaigns(self, status: Optional[str] = None) -> List[HeyReachCampaign]:
        payload: Dict[str, Any] = {}
        if status:
            payload["status"] = status
        resp = self._heyreach_request("POST", "/campaign/GetAll", json=payload)

        data = resp.json() if resp.content else {}
        campaigns: List[HeyReachCampaign] = []
        for item in data.get("campaigns") or data.get("data") or data.get("items") or []:
            campaigns.append(
                HeyReachCampaign(
                    id=str(item.get("id") or item.get("campaign_id")),
                    name=item.get("name") or "Unnamed",
                    status=item.get("status") or "unknown",
                    metadata=item,
                )
            )
        return campaigns

    def list_linkedin_accounts(
        self,
        keyword: Optional[str] = None,
        limit: int = 100,
    ) -> List[HeyReachLinkedInAccount]:
        accounts: List[HeyReachLinkedInAccount] = []
        offset = 0
        while True:
            payload: Dict[str, Any] = {"offset": offset, "limit": limit}
            if keyword:
                payload["keyword"] = keyword
            resp = self._heyreach_request("POST", "/li_account/GetAll", json=payload)
            data = resp.json() if resp.content else {}
            items = data.get("items") or data.get("data") or []
            for item in items:
                accounts.append(
                    HeyReachLinkedInAccount(
                        id=str(item.get("id")),
                        email=item.get("emailAddress") or item.get("email"),
                        first_name=item.get("firstName"),
                        last_name=item.get("lastName"),
                        is_active=item.get("isActive"),
                        auth_is_valid=item.get("authIsValid"),
                        is_valid_navigator=item.get("isValidNavigator"),
                        is_valid_recruiter=item.get("isValidRecruiter"),
                        active_campaigns=item.get("activeCampaigns"),
                        metadata=item,
                    )
                )
            total = data.get("totalCount")
            if total is not None and offset + limit >= total:
                break
            if len(items) < limit:
                break
            offset += limit
        return accounts

    def enroll_prospects(
        self,
        prospects: List[HeyReachProspect],
        campaign_id: str,
        idempotency_keys: Optional[List[str]] = None,
    ) -> BatchOutcome:
        outcome = BatchOutcome()
        if not prospects:
            return outcome

        batches = _chunk(prospects, self.rate_limit.batch_size)
        for batch_idx, batch in enumerate(batches):
            payload = {
                "campaignId": campaign_id,
                "leads": [self._serialize_prospect(p) for p in batch],
            }
            if idempotency_keys:
                payload["idempotencyKeys"] = idempotency_keys[
                    batch_idx * self.rate_limit.batch_size : batch_idx * self.rate_limit.batch_size + len(batch)
                ]

            try:
                resp = self._heyreach_request(
                    "POST",
                    "/campaign/AddLeadsToCampaignV2",
                    json=payload,
                    allow_statuses=(409,),
                )
            except RateLimitError:
                outcome.retryable_failed.extend(
                    [
                        {
                            "prospect_email": p.email,
                            "status": "retry",
                            "campaign_id": campaign_id,
                            "error": "Rate limited",
                        }
                        for p in batch
                    ]
                )
                continue
            except HeyReachError as exc:
                outcome.failed.extend(
                    [
                        {
                            "prospect_email": p.email,
                            "status": "failed",
                            "campaign_id": campaign_id,
                            "error": str(exc),
                        }
                        for p in batch
                    ]
                )
                continue

            if resp.status_code == 409:
                for p in batch:
                    outcome.succeeded.append(
                        {
                            "prospect_email": p.email,
                            "status": "duplicate",
                            "campaign_id": campaign_id,
                        }
                    )
                continue

            data = resp.json() if resp.content else {}
            ids = data.get("prospect_ids") or data.get("data") or []
            for i, prospect in enumerate(batch):
                provider_id = None
                try:
                    provider_id = ids[i] if i < len(ids) else None
                except Exception:
                    provider_id = None
                outcome.succeeded.append(
                    {
                        "prospect_email": prospect.email,
                        "status": "enrolled",
                        "campaign_id": campaign_id,
                        "provider_prospect_id": provider_id,
                    }
                )

            # Respect pacing
            time.sleep(self.rate_limit.delay_between_batches_ms / 1000.0)

        return outcome

    def get_prospect_status(self, campaign_id: str, email: str) -> Optional[Dict[str, Any]]:
        payload = {"campaignId": campaign_id, "email": email}
        try:
            resp = self._heyreach_request(
                "POST",
                "/campaign/GetLeadsFromCampaign",
                json=payload,
                allow_statuses=(404,),
            )
        except HeyReachError:
            return None
        if resp.status_code == 404:
            return None
        return resp.json() if resp.content else {}

    def _serialize_prospect(self, prospect: HeyReachProspect) -> Dict[str, Any]:
        data: Dict[str, Any] = {"email": prospect.email}
        if prospect.linkedin_url:
            data["linkedinProfile"] = prospect.linkedin_url
        if prospect.sender_account_id:
            data["senderAccountId"] = prospect.sender_account_id
        if prospect.first_name:
            data["firstName"] = prospect.first_name
        if prospect.last_name:
            data["lastName"] = prospect.last_name
        if prospect.company:
            data["company"] = prospect.company
        if prospect.title:
            data["title"] = prospect.title
        if prospect.custom_vars:
            data["customVariables"] = prospect.custom_vars
        return data


def build_idempotency_key(campaign_id: str, email: str) -> str:
    digest = hashlib.sha256(email.strip().lower().encode("utf-8")).hexdigest()
    return f"hr:{campaign_id}:{digest[:12]}"


def _chunk(items: List[Any], size: int) -> List[List[Any]]:
    return [items[i : i + size] for i in range(0, len(items), size)]


__all__ = [
    "HeyReachClient",
    "HeyReachProspect",
    "HeyReachCampaign",
    "HeyReachLinkedInAccount",
    "EnrollmentResult",
    "ActivationLogEntry",
    "ActivationProvider",
    "RateLimitConfig",
    "HEYREACH_RATE_LIMIT",
    "HeyReachError",
    "HeyReachAuthError",
    "build_idempotency_key",
]
