"""
Data models for sequencer integrations.

Provides normalized data structures for prospects, enrollment results,
campaigns, and webhook events across different sequencer providers.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime
from typing import Any, Dict, List, Literal, Optional


@dataclass
class Prospect:
    """
    A contact/prospect to be enrolled in a sequencer campaign.

    Required: email
    All other fields are optional and will be passed to the provider
    if supported.
    """

    email: str
    first_name: Optional[str] = None
    last_name: Optional[str] = None
    company: Optional[str] = None
    title: Optional[str] = None
    linkedin_url: Optional[str] = None
    domain: Optional[str] = None
    phone: Optional[str] = None
    custom_vars: Dict[str, Any] = field(default_factory=dict)
    send_email_from_email_account_id: Optional[str] = None
    # External IDs for writeback
    sfdc_contact_id: Optional[str] = None
    sfdc_account_id: Optional[str] = None
    sfdc_lead_id: Optional[str] = None

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary, excluding None values."""
        result = {"email": self.email}
        if self.first_name:
            result["first_name"] = self.first_name
        if self.last_name:
            result["last_name"] = self.last_name
        if self.company:
            result["company"] = self.company
        if self.title:
            result["title"] = self.title
        if self.linkedin_url:
            result["linkedin_url"] = self.linkedin_url
        if self.domain:
            result["domain"] = self.domain
        if self.phone:
            result["phone"] = self.phone
        if self.custom_vars:
            result["custom_vars"] = self.custom_vars
        return result


@dataclass
class Campaign:
    """A sequencer campaign/sequence."""

    id: str
    name: str
    status: Optional[str] = None
    created_at: Optional[datetime] = None
    # Provider-specific metadata
    metadata: Dict[str, Any] = field(default_factory=dict)

    def to_dict(self) -> Dict[str, Any]:
        return {
            "id": self.id,
            "name": self.name,
            "status": self.status,
            "created_at": self.created_at.isoformat() if self.created_at else None,
            "metadata": self.metadata,
        }


EnrollmentStatus = Literal["enrolled", "exists", "skipped", "failed", "pending", "duplicate"]


@dataclass
class EnrollmentResult:
    """Result of enrolling a single prospect in a campaign."""

    email: str
    campaign_id: str
    status: EnrollmentStatus
    provider_prospect_id: Optional[str] = None
    error: Optional[str] = None

    def __post_init__(self) -> None:
        # Normalize legacy provider status to contract vocabulary.
        if self.status == "duplicate":
            self.status = "exists"

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


@dataclass
class BatchOutcome:
    """
    Outcome of a batch operation (push/enroll).

    Tracks succeeded, failed, and skipped records for partial success handling.
    """

    succeeded: List[EnrollmentResult] = field(default_factory=list)
    failed: List[EnrollmentResult] = field(default_factory=list)
    skipped: List[EnrollmentResult] = field(default_factory=list)
    last_status_code: Optional[int] = None

    @property
    def total(self) -> int:
        return len(self.succeeded) + len(self.failed) + len(self.skipped)

    @property
    def status(self) -> str:
        """Overall status: success, partial, or error."""
        if not self.failed and not self.skipped:
            return "success"
        if not self.succeeded:
            return "error"
        return "partial"

    def to_dict(self) -> Dict[str, Any]:
        return {
            "total": self.total,
            "succeeded": len(self.succeeded),
            "failed": len(self.failed),
            "skipped_duplicates": len(self.skipped),
            "status": self.status,
            "last_status_code": self.last_status_code,
        }


EventType = Literal[
    "sent",
    "opened",
    "clicked",
    "replied",
    "bounced",
    "unsubscribed",
    "meeting_booked",
    "category_updated",
    "auto_replied",
    "out_of_office",
]


@dataclass
class NormalizedEvent:
    """
    A normalized webhook event from any sequencer provider.

    Provides a consistent structure for processing events from
    Instantly, Smartlead, Apollo, or Outreach.
    """

    event_type: EventType
    timestamp: datetime
    provider: Literal["instantly", "smartlead", "apollo", "outreach"]
    provider_event_id: str
    email: str
    campaign_id: str
    campaign_name: Optional[str] = None
    # Reply-specific
    reply_text: Optional[str] = None
    reply_subject: Optional[str] = None
    reply_html: Optional[str] = None
    # Bounce-specific
    bounce_type: Optional[str] = None
    # Meeting-specific
    meeting_time: Optional[datetime] = None
    # Category/label update
    category: Optional[str] = None
    # Raw payload for debugging
    raw_payload: Dict[str, Any] = field(default_factory=dict)

    def to_dict(self) -> Dict[str, Any]:
        result = {
            "event_type": self.event_type,
            "timestamp": self.timestamp.isoformat(),
            "provider": self.provider,
            "provider_event_id": self.provider_event_id,
            "email": self.email,
            "campaign_id": self.campaign_id,
        }
        if self.campaign_name:
            result["campaign_name"] = self.campaign_name
        if self.reply_text:
            result["reply_text"] = self.reply_text
        if self.reply_subject:
            result["reply_subject"] = self.reply_subject
        if self.bounce_type:
            result["bounce_type"] = self.bounce_type
        if self.meeting_time:
            result["meeting_time"] = self.meeting_time.isoformat()
        if self.category:
            result["category"] = self.category
        return result


@dataclass
class RateLimitConfig:
    """Rate limiting configuration for a provider."""

    requests_per_window: int
    window_seconds: int
    batch_size: int

    @property
    def requests_per_second(self) -> float:
        return self.requests_per_window / self.window_seconds


# Provider-specific rate limit configurations
SMARTLEAD_RATE_LIMIT = RateLimitConfig(
    requests_per_window=60,
    window_seconds=60,
    batch_size=350,  # Hard cap enforced by API
)

INSTANTLY_RATE_LIMIT = RateLimitConfig(
    requests_per_window=30,  # Conservative - not documented
    window_seconds=60,
    batch_size=100,  # Safe default
)

APOLLO_RATE_LIMIT = RateLimitConfig(
    requests_per_window=50,  # Apollo has tiered limits; conservative default
    window_seconds=60,
    batch_size=50,  # Apollo enrolls one at a time, so batch = concurrent requests
)

OUTREACH_RATE_LIMIT = RateLimitConfig(
    requests_per_window=50,
    window_seconds=60,
    batch_size=50,
)
