"""
Abstract provider interface for sales sequencers.

Defines the Protocol that all sequencer providers (Instantly, Smartlead, etc.)
must implement for a consistent integration experience.
"""

from __future__ import annotations

from abc import abstractmethod
from dataclasses import dataclass
from typing import Any, Dict, List, Optional, Protocol, runtime_checkable

from g_gremlin.sequencer.models import (
    BatchOutcome,
    Campaign,
    NormalizedEvent,
    Prospect,
    RateLimitConfig,
)


@dataclass
class WebhookRequest:
    """Incoming webhook request data."""

    headers: Dict[str, str]
    body: bytes
    json_payload: Dict[str, Any]


@runtime_checkable
class SequencerProvider(Protocol):
    """
    Abstract interface for sales sequencer providers.

    Implementations must provide methods for:
    - Upserting prospects (create/update + optional enrollment)
    - Enrolling existing prospects in campaigns
    - Listing available campaigns
    - Verifying webhook signatures (if supported)
    - Normalizing webhook events to a common format
    """

    @property
    @abstractmethod
    def name(self) -> str:
        """Provider name (e.g., 'instantly', 'smartlead')."""
        ...

    @property
    @abstractmethod
    def rate_limit(self) -> RateLimitConfig:
        """Rate limiting configuration for this provider."""
        ...

    @abstractmethod
    def upsert_prospects(
        self,
        prospects: List[Prospect],
        campaign_id: Optional[str] = None,
    ) -> BatchOutcome:
        """
        Create or update prospects, optionally enrolling in a campaign.

        Args:
            prospects: List of prospects to upsert
            campaign_id: Optional campaign to enroll prospects in

        Returns:
            BatchOutcome with succeeded, failed, and skipped results
        """
        ...

    @abstractmethod
    def enroll_in_campaign(
        self,
        prospect_ids: List[str],
        campaign_id: str,
    ) -> BatchOutcome:
        """
        Enroll existing prospects in a campaign by their provider IDs.

        Args:
            prospect_ids: List of provider-assigned prospect IDs
            campaign_id: Campaign to enroll in

        Returns:
            BatchOutcome with succeeded, failed, and skipped results
        """
        ...

    @abstractmethod
    def list_campaigns(self) -> List[Campaign]:
        """
        List available campaigns/sequences.

        Returns:
            List of Campaign objects
        """
        ...

    @abstractmethod
    def create_campaign(self, name: str, **kwargs) -> Campaign:
        """
        Create a new campaign/sequence.

        Args:
            name: Campaign name
            **kwargs: Provider-specific options (e.g., client_id for Smartlead,
                     timezone for Instantly)

        Returns:
            The created Campaign object

        Raises:
            SequencerError: If campaign creation fails or is not supported
        """
        ...

    @abstractmethod
    def verify_webhook(self, request: WebhookRequest) -> bool:
        """
        Verify webhook signature (if supported by provider).

        Args:
            request: Incoming webhook request

        Returns:
            True if signature is valid or provider doesn't use signatures,
            False if signature is invalid
        """
        ...

    @abstractmethod
    def normalize_event(self, raw_event: Dict[str, Any]) -> NormalizedEvent:
        """
        Convert provider-specific webhook event to normalized format.

        Args:
            raw_event: Raw webhook payload from provider

        Returns:
            NormalizedEvent with consistent structure
        """
        ...

    @abstractmethod
    def test_connection(self) -> Dict[str, Any]:
        """
        Test API connectivity and return account info.

        Used by doctor command to verify credentials.

        Returns:
            Dict with connection status and account details
            e.g., {"status": "ok", "plan": "Growth", "webhooks_available": True}

        Raises:
            SequencerAuthError: If authentication fails
            SequencerError: If connection fails
        """
        ...


class BaseProvider:
    """
    Base class with shared functionality for sequencer providers.

    Provides common HTTP client setup, retry logic, and rate limiting.
    """

    def __init__(
        self,
        api_key: str,
        base_url: str,
        timeout: int = 30,
    ):
        self.api_key = api_key
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout
        self._session = None

    def _get_session(self):
        """Lazy-load requests session."""
        if self._session is None:
            import requests

            self._session = requests.Session()
        return self._session

    def _headers(self) -> Dict[str, str]:
        """Override in subclass to provide auth headers."""
        return {
            "Content-Type": "application/json",
            "Accept": "application/json",
        }

    def _check_duplicate_response(self, response: Any) -> bool:
        """
        Check if API response indicates a duplicate/already exists.

        Override in subclass with provider-specific logic.
        """
        return False

    def close(self):
        """Close the HTTP session."""
        if self._session is not None:
            self._session.close()
            self._session = None

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()
        return False
