"""
Smartlead sequencer provider implementation.

API Reference: https://api.smartlead.ai/reference
"""

from __future__ import annotations

import hashlib
import hmac
from datetime import datetime
from email.utils import parsedate_to_datetime
from typing import Any, Dict, List, Optional

from g_gremlin.sequencer import (
    SequencerAuthError,
    SequencerError,
    SequencerRateLimitError,
)
from g_gremlin.sequencer.models import (
    SMARTLEAD_RATE_LIMIT,
    BatchOutcome,
    Campaign,
    EnrollmentResult,
    NormalizedEvent,
    Prospect,
    RateLimitConfig,
)
from g_gremlin.sequencer.provider import BaseProvider, WebhookRequest


# Event type mapping from Smartlead to normalized types
SMARTLEAD_EVENT_MAP = {
    "EMAIL_SENT": "sent",
    "EMAIL_OPEN": "opened",
    "EMAIL_LINK_CLICK": "clicked",
    "EMAIL_REPLY": "replied",
    "LEAD_UNSUBSCRIBED": "unsubscribed",
    "LEAD_CATEGORY_UPDATED": "category_updated",
    "campaign_bounce_threshold": "bounced",
}


class SmartleadProvider(BaseProvider):
    """
    Smartlead sequencer provider.

    Implements the SequencerProvider protocol for Smartlead API.
    """

    BASE_URL = "https://server.smartlead.ai/api/v1"

    def __init__(
        self,
        api_key: str,
        webhook_secret: Optional[str] = None,
        timeout: int = 30,
        base_url: Optional[str] = None,
        use_bearer_auth: bool = False,  # Accepted for interface consistency; Smartlead uses query param auth
    ):
        super().__init__(
            api_key=api_key,
            base_url=base_url or self.BASE_URL,
            timeout=timeout,
        )
        self.webhook_secret = webhook_secret
        # use_bearer_auth is ignored - Smartlead authenticates via api_key query parameter

    @property
    def name(self) -> str:
        return "smartlead"

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

    def _headers(self) -> Dict[str, str]:
        """Smartlead uses standard JSON headers (auth via query param)."""
        return {
            "Content-Type": "application/json",
            "Accept": "application/json",
        }

    def _request(
        self,
        method: str,
        endpoint: str,
        json: Optional[Dict] = None,
        params: Optional[Dict] = None,
    ) -> Dict[str, Any]:
        """Make authenticated request to Smartlead API."""
        session = self._get_session()
        url = f"{self.base_url}/{endpoint.lstrip('/')}"

        # Add API key as query parameter
        if params is None:
            params = {}
        params["api_key"] = self.api_key

        resp = session.request(
            method=method,
            url=url,
            headers=self._headers(),
            json=json,
            params=params,
            timeout=self.timeout,
        )

        if resp.status_code == 401 or resp.status_code == 403:
            raise SequencerAuthError("Smartlead API key is invalid or expired")
        if resp.status_code == 429:
            retry_after = self._parse_retry_after(resp.headers.get("Retry-After"))
            raise SequencerRateLimitError(
                "Smartlead rate limit exceeded",
                retry_after=retry_after,
            )
        if resp.status_code >= 400:
            try:
                error_detail = resp.json()
            except Exception:
                error_detail = resp.text
            raise SequencerError(f"Smartlead API error {resp.status_code}: {error_detail}")

        return resp.json() if resp.content else {}

    @staticmethod
    def _parse_retry_after(value: Optional[str]) -> Optional[float]:
        if not value:
            return None
        try:
            return float(value)
        except ValueError:
            try:
                dt = parsedate_to_datetime(value)
                if dt.tzinfo:
                    return max(0.0, (dt - datetime.utcnow().replace(tzinfo=dt.tzinfo)).total_seconds())
            except Exception:
                return None
        return None

    @staticmethod
    def _extract_result_items(payload: Dict[str, Any]) -> List[Dict[str, Any]]:
        data = payload.get("data") if isinstance(payload.get("data"), dict) else {}
        for container in (data, payload):
            if not isinstance(container, dict):
                continue
            for key in ("leads", "lead_list", "added_leads", "created_leads", "results", "items"):
                items = container.get(key)
                if isinstance(items, list):
                    return [item for item in items if isinstance(item, dict)]
        return []

    @staticmethod
    def _extract_result_email(item: Dict[str, Any]) -> str:
        for key in ("email", "lead_email", "email_address"):
            value = item.get(key)
            if value:
                text = str(value).strip().lower()
                if text:
                    return text
        return ""

    @staticmethod
    def _extract_result_index(item: Dict[str, Any]) -> Optional[int]:
        for key in ("index", "lead_index", "position"):
            value = item.get(key)
            if value is None:
                continue
            try:
                return int(value)
            except (TypeError, ValueError):
                continue
        return None

    @staticmethod
    def _extract_result_provider_id(item: Dict[str, Any]) -> Optional[str]:
        for key in (
            "id",
            "lead_id",
            "leadId",
            "prospect_id",
            "prospectId",
            "person_id",
            "personId",
            "contact_id",
            "contactId",
        ):
            value = item.get(key)
            if value is None:
                continue
            text = str(value).strip()
            if text:
                return text
        return None

    def _prospect_to_smartlead(self, prospect: Prospect) -> Dict:
        """Convert Prospect to Smartlead lead format."""
        lead = {
            "email": prospect.email,
        }
        if prospect.first_name:
            lead["first_name"] = prospect.first_name
        if prospect.last_name:
            lead["last_name"] = prospect.last_name
        if prospect.company:
            lead["company_name"] = prospect.company
        if prospect.phone:
            lead["phone_number"] = prospect.phone
        if prospect.linkedin_url:
            lead["linkedin_profile"] = prospect.linkedin_url
        if prospect.domain:
            lead["website"] = prospect.domain

        # Build custom_fields object for title and custom_vars
        # Smartlead doesn't accept 'title' as a standard field
        custom_fields: Dict[str, str] = {}
        if prospect.title:
            custom_fields["title"] = prospect.title
        if prospect.custom_vars:
            for key, value in prospect.custom_vars.items():
                custom_fields[key] = str(value) if value is not None else ""

        if custom_fields:
            lead["custom_fields"] = custom_fields

        return lead

    def upsert_prospects(
        self,
        prospects: List[Prospect],
        campaign_id: Optional[str] = None,
    ) -> BatchOutcome:
        """
        Add prospects to Smartlead campaign.

        Smartlead requires a campaign_id - leads are always added to a campaign.
        Batch limit: 350 leads per request.
        """
        outcome = BatchOutcome()

        if not prospects:
            return outcome

        if not campaign_id:
            raise SequencerError("Smartlead requires a campaign_id to add leads")

        # Convert to Smartlead format
        leads = [self._prospect_to_smartlead(p) for p in prospects]

        try:
            # Smartlead endpoint: add-leads-to-a-campaign-by-id
            payload = {
                "lead_list": leads,
            }

            resp = self._request(
                "POST",
                f"/campaigns/{campaign_id}/leads",
                json=payload,
            )
            outcome.last_status_code = 200

            # Process response
            # Smartlead returns: {"ok": true, "data": {...}}
            if resp.get("ok") or resp.get("success"):
                items = self._extract_result_items(resp)
                ids_by_email: Dict[str, str] = {}
                ids_by_index: Dict[int, str] = {}
                for pos, item in enumerate(items):
                    provider_id = self._extract_result_provider_id(item)
                    if not provider_id:
                        continue
                    item_email = self._extract_result_email(item)
                    if item_email:
                        ids_by_email[item_email] = provider_id
                    item_index = self._extract_result_index(item)
                    if item_index is None:
                        item_index = pos
                    ids_by_index[item_index] = provider_id

                # All leads added successfully
                for idx, prospect in enumerate(prospects):
                    provider_id = (
                        ids_by_email.get((prospect.email or "").strip().lower())
                        or ids_by_index.get(idx)
                    )
                    outcome.succeeded.append(EnrollmentResult(
                        email=prospect.email,
                        campaign_id=campaign_id,
                        status="enrolled",
                        provider_prospect_id=provider_id,
                    ))
            else:
                # Check for specific errors
                error_msg = resp.get("message", resp.get("error", "Unknown error"))

                # Check if it's a duplicate error
                if "already" in str(error_msg).lower() or "duplicate" in str(error_msg).lower():
                    items = self._extract_result_items(resp)
                    ids_by_email: Dict[str, str] = {}
                    ids_by_index: Dict[int, str] = {}
                    for pos, item in enumerate(items):
                        provider_id = self._extract_result_provider_id(item)
                        if not provider_id:
                            continue
                        item_email = self._extract_result_email(item)
                        if item_email:
                            ids_by_email[item_email] = provider_id
                        item_index = self._extract_result_index(item)
                        if item_index is None:
                            item_index = pos
                        ids_by_index[item_index] = provider_id

                    for idx, prospect in enumerate(prospects):
                        provider_id = (
                            ids_by_email.get((prospect.email or "").strip().lower())
                            or ids_by_index.get(idx)
                        )
                        outcome.skipped.append(EnrollmentResult(
                            email=prospect.email,
                            campaign_id=campaign_id,
                            status="exists",
                            provider_prospect_id=provider_id,
                        ))
                else:
                    for prospect in prospects:
                        outcome.failed.append(EnrollmentResult(
                            email=prospect.email,
                            campaign_id=campaign_id,
                            status="failed",
                            error=error_msg,
                        ))

        except SequencerRateLimitError:
            outcome.last_status_code = 429
            for prospect in prospects:
                outcome.failed.append(EnrollmentResult(
                    email=prospect.email,
                    campaign_id=campaign_id,
                    status="failed",
                    error="Rate limit exceeded",
                ))
            raise

        except SequencerError as e:
            for prospect in prospects:
                outcome.failed.append(EnrollmentResult(
                    email=prospect.email,
                    campaign_id=campaign_id,
                    status="failed",
                    error=str(e),
                ))

        return outcome

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

        Note: Smartlead uses email as the primary identifier,
        so this method may need email addresses instead of IDs.
        """
        outcome = BatchOutcome()

        # Smartlead doesn't have a direct "move leads" endpoint
        # This would require fetching lead details and re-adding
        for lead_id in prospect_ids:
            outcome.failed.append(EnrollmentResult(
                email="",
                campaign_id=campaign_id,
                status="failed",
                provider_prospect_id=lead_id,
                error="Smartlead does not support moving leads by ID",
            ))

        return outcome

    def list_campaigns(self) -> List[Campaign]:
        """List all Smartlead campaigns."""
        campaigns = []

        resp = self._request("GET", "/campaigns")

        # Handle response format
        items = resp if isinstance(resp, list) else resp.get("data", resp.get("campaigns", []))

        for item in items:
            campaigns.append(Campaign(
                id=str(item.get("id", "")),
                name=item.get("name", ""),
                status=item.get("status"),
                metadata={
                    "created_at": item.get("created_at"),
                    "updated_at": item.get("updated_at"),
                    "lead_count": item.get("lead_count"),
                },
            ))

        return campaigns

    def create_campaign(self, name: str, client_id: Optional[int] = None, **kwargs) -> Campaign:
        """
        Create a new Smartlead campaign.

        Args:
            name: Campaign name
            client_id: Optional client ID to associate with the campaign

        Returns:
            The created Campaign object
        """
        payload: Dict[str, Any] = {"name": name}
        if client_id is not None:
            payload["client_id"] = client_id

        resp = self._request("POST", "/campaigns/create", json=payload)

        return Campaign(
            id=str(resp.get("id", "")),
            name=resp.get("name", name),
            status="active",
            metadata={
                "created_at": resp.get("created_at"),
            },
        )

    def list_email_accounts(self) -> List[Dict[str, Any]]:
        """List Smartlead mailboxes/email accounts."""
        endpoints = ["/mailboxes", "/email-accounts"]
        last_error: Optional[Exception] = None

        for endpoint in endpoints:
            try:
                resp = self._request("GET", endpoint)
            except SequencerError as exc:
                last_error = exc
                if "404" in str(exc):
                    continue
                raise

            items = resp
            if isinstance(resp, dict):
                items = resp.get("data") or resp.get("mailboxes") or resp.get("accounts") or []
            if not isinstance(items, list):
                continue

            accounts: List[Dict[str, Any]] = []
            for item in items:
                if not isinstance(item, dict):
                    continue
                accounts.append({
                    "id": item.get("id") or item.get("mailbox_id") or item.get("email_account_id"),
                    "email": item.get("email") or item.get("email_address"),
                    "name": item.get("name") or item.get("sender_name") or item.get("from_name"),
                    "status": item.get("status") or item.get("state"),
                })
            return accounts

        if last_error and "404" not in str(last_error):
            raise last_error
        return []

    def verify_webhook(self, request: WebhookRequest) -> bool:
        """
        Verify Smartlead webhook signature using HMAC SHA256.

        Smartlead sends signature in X-Smartlead-Signature header.
        """
        if not self.webhook_secret:
            # If no secret configured, skip verification
            # (but log a warning in production)
            return True

        signature = request.headers.get("X-Smartlead-Signature", "")
        if not signature:
            return False

        # Compute expected signature
        expected = hmac.new(
            self.webhook_secret.encode("utf-8"),
            request.body,
            hashlib.sha256,
        ).hexdigest()

        return hmac.compare_digest(signature, expected)

    def normalize_event(self, raw_event: Dict[str, Any]) -> NormalizedEvent:
        """Convert Smartlead webhook payload to normalized event."""
        # Determine event type
        event_type_raw = raw_event.get("event_type", raw_event.get("type", ""))
        event_type = SMARTLEAD_EVENT_MAP.get(event_type_raw, "category_updated")

        # Parse timestamp
        timestamp_str = raw_event.get("event_timestamp", raw_event.get("time_replied", ""))
        try:
            if timestamp_str:
                timestamp = datetime.fromisoformat(timestamp_str.replace("Z", "+00:00"))
            else:
                timestamp = datetime.utcnow()
        except (ValueError, AttributeError):
            timestamp = datetime.utcnow()

        # Build normalized event
        event = NormalizedEvent(
            event_type=event_type,
            timestamp=timestamp,
            provider="smartlead",
            provider_event_id=str(raw_event.get("stats_id", raw_event.get("message_id", ""))),
            email=raw_event.get("sl_lead_email", raw_event.get("to_email", "")),
            campaign_id=str(raw_event.get("campaign_id", "")),
            campaign_name=raw_event.get("campaign_name"),
            raw_payload=raw_event,
        )

        # Add reply-specific fields
        if event_type == "replied":
            event.reply_text = raw_event.get("preview_text", raw_event.get("description", ""))
            event.reply_subject = raw_event.get("subject")

        # Add category for category update events
        if event_type == "category_updated":
            event.category = raw_event.get("category", raw_event.get("lead_category"))

        # Add bounce info
        if event_type == "bounced":
            event.bounce_type = "threshold"  # Smartlead uses bounce threshold events

        return event

    def test_connection(self) -> Dict[str, Any]:
        """Test API connectivity and return account info."""
        try:
            # List campaigns to verify credentials
            campaigns = self.list_campaigns()

            return {
                "status": "ok",
                "provider": "smartlead",
                "campaign_count": len(campaigns),
                "webhooks_available": True,  # Available on PRO plan
            }
        except SequencerAuthError:
            return {
                "status": "error",
                "provider": "smartlead",
                "error": "Authentication failed - check API key",
            }
        except SequencerError as e:
            return {
                "status": "error",
                "provider": "smartlead",
                "error": str(e),
            }
