"""
JustCall client helpers.

Supports:
- doctor: simple auth check
- list_calls: GET /calls with filters
- send_sms: POST /texts/new
- send_whatsapp: POST /whatsapp/messages/send
- Sales Dialer: campaigns, bulk import, DNCA
- Webhook signature verification
"""

from __future__ import annotations

import hashlib
import hmac
import re
import urllib.parse
from dataclasses import dataclass, field
from typing import Any, Dict, List, Literal, Optional

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


V1_BASE = "https://api.justcall.io/v1"
V2_BASE = "https://api.justcall.io/v2.1"


# ---------------------------------------------------------------------------
# Phone normalization utilities
# ---------------------------------------------------------------------------

def normalize_phone_e164(phone: str, default_country: str = "US") -> tuple[str, str | None, bool]:
    """
    Normalize phone to E.164 format.

    Returns:
        (normalized_phone, extension, is_valid)

    Examples:
        "+1 (555) 123-4567 x123" -> ("+15551234567", "123", True)
        "555-123-4567" -> ("+15551234567", None, True)  # assumes US
        "invalid" -> ("invalid", None, False)
    """
    if not phone:
        return ("", None, False)

    original = phone.strip()

    # Extract extension
    extension = None
    ext_match = re.search(r"(?:x|ext\.?|extension)\s*(\d+)", original, re.IGNORECASE)
    if ext_match:
        extension = ext_match.group(1)
        original = original[:ext_match.start()].strip()

    # Remove all non-digit characters except leading +
    has_plus = original.startswith("+")
    digits = re.sub(r"\D", "", original)

    if not digits:
        return (phone.strip(), extension, False)

    # Normalize to E.164
    if has_plus:
        # Already has country code
        normalized = f"+{digits}"
    elif len(digits) == 10 and default_country == "US":
        # US number without country code
        normalized = f"+1{digits}"
    elif len(digits) == 11 and digits.startswith("1") and default_country == "US":
        # US number with leading 1
        normalized = f"+{digits}"
    else:
        # Assume it needs a +
        normalized = f"+{digits}"

    # Basic validation: E.164 should be 8-15 digits after +
    digit_count = len(normalized) - 1  # exclude +
    is_valid = 8 <= digit_count <= 15

    return (normalized, extension, is_valid)


def validate_phone_batch(
    phones: List[str],
    default_country: str = "US",
) -> List[Dict[str, Any]]:
    """
    Validate a batch of phone numbers.

    Returns list of dicts with:
        original, normalized, extension, is_valid, sms_ok, call_ok
    """
    results = []
    for phone in phones:
        normalized, extension, is_valid = normalize_phone_e164(phone, default_country)
        results.append({
            "original": phone,
            "normalized": normalized,
            "extension": extension,
            "is_valid": is_valid,
            "sms_ok": is_valid,  # SMS requires valid E.164
            "call_ok": is_valid,  # Calls require valid E.164
        })
    return results


# ---------------------------------------------------------------------------
# Data models for Sales Dialer
# ---------------------------------------------------------------------------

@dataclass
class Campaign:
    """JustCall Sales Dialer campaign."""
    id: str
    name: str
    status: str  # active, paused, completed
    type: str  # autodialer, predictive, dynamic
    created_at: Optional[str] = None
    contact_count: int = 0
    metadata: Dict[str, Any] = field(default_factory=dict)


@dataclass
class CampaignContact:
    """Contact to be added to a campaign."""
    phone: str  # E.164 format required
    first_name: Optional[str] = None
    last_name: Optional[str] = None
    email: Optional[str] = None
    company: Optional[str] = None
    custom_fields: Dict[str, str] = field(default_factory=dict)

    def to_api_dict(self) -> Dict[str, Any]:
        """Convert to JustCall API format."""
        data: Dict[str, Any] = {"phone": self.phone}
        if self.first_name:
            data["first_name"] = self.first_name
        if self.last_name:
            data["last_name"] = self.last_name
        if self.email:
            data["email"] = self.email
        if self.company:
            data["company"] = self.company
        if self.custom_fields:
            data["custom_fields"] = self.custom_fields
        return data


@dataclass
class BulkImportResult:
    """Result of bulk contact import."""
    success_count: int
    failed_count: int
    import_id: Optional[str] = None
    errors: List[Dict[str, Any]] = field(default_factory=list)


# ---------------------------------------------------------------------------
# Webhook signature verification
# ---------------------------------------------------------------------------

def verify_webhook_signature(
    api_secret: str,
    webhook_url: str,
    event_type: str,
    timestamp: str,
    received_signature: str,
) -> bool:
    """
    Verify JustCall webhook signature.

    Signature format: {api_secret}|{encoded_url}|{event_type}|{timestamp}
    Algorithm: SHA256 HMAC

    Args:
        api_secret: Your JustCall API secret
        webhook_url: The webhook endpoint URL
        event_type: Event type (e.g., "call.completed")
        timestamp: Value from x-justcall-request-timestamp header
        received_signature: Value from x-justcall-signature header

    Returns:
        True if signature is valid
    """
    encoded_url = urllib.parse.quote(webhook_url, safe="")
    payload = f"{api_secret}|{encoded_url}|{event_type}|{timestamp}"

    expected_signature = hmac.new(
        api_secret.encode("utf-8"),
        payload.encode("utf-8"),
        hashlib.sha256,
    ).hexdigest()

    return hmac.compare_digest(expected_signature, received_signature)


class JustCallError(RuntimeError):
    """Base error for JustCall operations."""


class JustCallAuthError(JustCallError):
    """Authentication failed."""


class JustCallRateLimitError(JustCallError):
    """429 error."""


@dataclass
class JustCallClient(BaseIntegration):
    SERVICE_NAME = "justcall"
    api_key: str
    api_secret: str
    base_url: str = V2_BASE
    timeout: int = 30

    def __init__(
        self,
        api_key: Optional[str] = None,
        api_secret: Optional[str] = None,
        credential_provider: CredentialProvider | None = None,
        retry_config: RetryConfig | None = None,
        base_url: str = V2_BASE,
    ) -> None:
        super().__init__(credential_provider=credential_provider, default_retry_config=retry_config or RetryConfig(max_attempts=4, base_delay=1.0, max_delay=12.0))
        self.base_url = base_url
        self.api_key, self.api_secret = self._load_creds(api_key, api_secret)

    def _load_creds(self, api_key: Optional[str], api_secret: Optional[str]) -> tuple[str, str]:
        if api_key and api_secret:
            return api_key, api_secret
        try:
            creds = resolve_credentials(
                "justcall",
                required_keys=["api_key", "api_secret"],
                credential_provider=self.credential_provider,
            )
            return creds["api_key"], creds["api_secret"]
        except SecretNotFoundError:
            raise JustCallAuthError(
                "JustCall not configured. Set G_GREMLIN_JUSTCALL_API_KEY and G_GREMLIN_JUSTCALL_API_SECRET or run g-gremlin auth set justcall."
            )

    def _headers(self) -> Dict[str, str]:
        return {
            "Authorization": f"{self.api_key}:{self.api_secret}",
            "Content-Type": "application/json",
            "Accept": "application/json",
        }

    def _jc_request(
        self,
        method: str,
        path: str,
        params: Optional[Dict[str, Any]] = None,
        json: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, Any]:
        url = f"{self.base_url}/{path.lstrip('/')}"
        try:
            resp = super()._request(
                method=method,
                url=url,
                params=params,
                json=json,
                headers=self._headers(),
                timeout=self.timeout,
            )
        except AuthenticationError as exc:
            raise JustCallAuthError(str(exc)) from exc
        except RateLimitError as exc:
            raise JustCallRateLimitError(str(exc)) from exc
        except IntegrationError as exc:
            raise JustCallError(str(exc)) from exc

        return resp.json() if hasattr(resp, "json") else resp

    def _jc_v1_request(
        self,
        method: str,
        path: str,
        params: Optional[Dict[str, Any]] = None,
        json: Optional[Dict[str, Any]] = None,
        allow_statuses: tuple[int, ...] = (),
    ) -> Dict[str, Any]:
        """Make request to v1 Sales Dialer API."""
        url = f"{V1_BASE}/{path.lstrip('/')}"
        try:
            resp = super()._request(
                method=method,
                url=url,
                params=params,
                json=json,
                headers=self._headers(),
                timeout=self.timeout,
                allow_statuses=allow_statuses,
            )
        except AuthenticationError as exc:
            raise JustCallAuthError(str(exc)) from exc
        except RateLimitError as exc:
            raise JustCallRateLimitError(str(exc)) from exc
        except IntegrationError as exc:
            raise JustCallError(str(exc)) from exc
        if hasattr(resp, "json"):
            try:
                return resp.json() if resp.content else {}
            except ValueError:
                return {}
        return resp

    def doctor(self) -> Dict[str, Any]:
        """Auth check via lightweight calls fetch."""
        return self._jc_request("GET", "/calls", params={"per_page": 1})

    def list_calls(self, params: Dict[str, Any]) -> Dict[str, Any]:
        """List calls with optional filters."""
        return self._jc_request("GET", "/calls", params=params)

    def send_sms(
        self,
        justcall_number: str,
        contact_number: str,
        body: str,
        media_url: Optional[str] = None,
        restrict_once: Optional[str] = None,
        schedule_at: Optional[str] = None,
    ) -> Dict[str, Any]:
        payload: Dict[str, Any] = {
            "justcall_number": justcall_number,
            "contact_number": contact_number,
            "body": body,
        }
        if media_url:
            payload["media_url"] = media_url
        if restrict_once:
            payload["restrict_once"] = restrict_once
        if schedule_at:
            payload["schedule_at"] = schedule_at
        return self._jc_request("POST", "/texts/new", json=payload)

    def send_whatsapp(
        self,
        justcall_number: str,
        contact_number: str,
        body: Optional[str] = None,
        template_id: Optional[int] = None,
        template_variables: Optional[list[str]] = None,
        media_url: Optional[str] = None,
        media_file_name: Optional[str] = None,
        restrict_once: Optional[str] = None,
    ) -> Dict[str, Any]:
        payload: Dict[str, Any] = {
            "justcall_number": justcall_number,
            "contact_number": contact_number,
        }
        if template_id is not None:
            payload["template_id"] = template_id
        if template_variables:
            payload["template_variables"] = template_variables
        if body:
            payload["body"] = body
        if media_url:
            payload["media_url"] = media_url
        if media_file_name:
            payload["media_file_name"] = media_file_name
        if restrict_once:
            payload["restrict_once"] = restrict_once
        return self._jc_request("POST", "/whatsapp/messages/send", json=payload)

    # -----------------------------------------------------------------------
    # Sales Dialer: Campaigns
    # -----------------------------------------------------------------------

    def list_campaigns(
        self,
        page: int = 1,
        per_page: int = 50,
        status: Optional[str] = None,
    ) -> List[Campaign]:
        """
        List Sales Dialer campaigns.

        Args:
            page: Page number (1-indexed)
            per_page: Results per page (max 100)
            status: Filter by status (active, paused, completed)

        Returns:
            List of Campaign objects
        """
        params: Dict[str, Any] = {"page": page, "per_page": per_page}
        if status:
            params["status"] = status

        data = self._jc_v1_request("POST", "autodialer/campaigns/list", json=params)
        campaigns = []
        for item in data.get("data", []) if isinstance(data, dict) else []:
            campaigns.append(Campaign(
                id=str(item.get("id", "")),
                name=item.get("name", ""),
                status=item.get("status", "unknown"),
                type=item.get("type", "autodialer"),
                created_at=item.get("created_at"),
                contact_count=item.get("contact_count", 0),
                metadata=item,
            ))
        return campaigns

    def get_campaign(self, campaign_id: str) -> Campaign:
        """Get details of a specific campaign."""
        data = self._jc_v1_request("GET", f"autodialer/campaigns/{campaign_id}")
        item = data.get("data", {}) if isinstance(data, dict) else {}
        return Campaign(
            id=str(item.get("id", campaign_id)),
            name=item.get("name", ""),
            status=item.get("status", "unknown"),
            type=item.get("type", "autodialer"),
            created_at=item.get("created_at"),
            contact_count=item.get("contact_count", 0),
            metadata=item,
        )

    def create_campaign(
        self,
        name: str,
        campaign_type: Literal["autodialer", "predictive", "dynamic"] = "autodialer",
    ) -> Campaign:
        """
        Create a new Sales Dialer campaign.

        Args:
            name: Campaign name
            campaign_type: Type of dialer (autodialer, predictive, dynamic)

        Returns:
            Created Campaign object
        """
        data = self._jc_v1_request(
            "POST",
            "autodialer/campaigns",
            json={"name": name, "type": campaign_type},
        )
        item = data.get("data", {}) if isinstance(data, dict) else {}
        return Campaign(
            id=str(item.get("id", "")),
            name=item.get("name", name),
            status=item.get("status", "active"),
            type=item.get("type", campaign_type),
            created_at=item.get("created_at"),
            contact_count=0,
            metadata=item,
        )

    # -----------------------------------------------------------------------
    # Sales Dialer: Bulk Contact Import
    # -----------------------------------------------------------------------

    def bulk_import_contacts(
        self,
        campaign_id: str,
        contacts: List[CampaignContact],
    ) -> BulkImportResult:
        """
        Bulk import contacts into a Sales Dialer campaign.

        Args:
            campaign_id: Target campaign ID
            contacts: List of contacts (max 250 per request)

        Returns:
            BulkImportResult with success/failure counts
        """
        if len(contacts) > 250:
            raise JustCallError("Maximum 250 contacts per bulk import request")

        payload = {
            "campaign_id": campaign_id,
            "contacts": [c.to_api_dict() for c in contacts],
        }

        data = self._jc_v1_request("POST", "autodialer/contacts/bulk-import", json=payload)
        return BulkImportResult(
            success_count=data.get("success_count", len(contacts)) if isinstance(data, dict) else len(contacts),
            failed_count=data.get("failed_count", 0) if isinstance(data, dict) else 0,
            import_id=data.get("import_id") if isinstance(data, dict) else None,
            errors=data.get("errors", []) if isinstance(data, dict) else [],
        )

    def bulk_import_contacts_batched(
        self,
        campaign_id: str,
        contacts: List[CampaignContact],
        batch_size: int = 250,
    ) -> BulkImportResult:
        """
        Bulk import contacts in batches of up to 250.

        Args:
            campaign_id: Target campaign ID
            contacts: List of contacts (any size)
            batch_size: Batch size (max 250)

        Returns:
            Aggregated BulkImportResult
        """
        batch_size = min(batch_size, 250)
        total_success = 0
        total_failed = 0
        all_errors: List[Dict[str, Any]] = []

        for i in range(0, len(contacts), batch_size):
            batch = contacts[i : i + batch_size]
            result = self.bulk_import_contacts(campaign_id, batch)
            total_success += result.success_count
            total_failed += result.failed_count
            all_errors.extend(result.errors)

        return BulkImportResult(
            success_count=total_success,
            failed_count=total_failed,
            errors=all_errors,
        )

    # -----------------------------------------------------------------------
    # Sales Dialer: DNCA (Do Not Call Again)
    # -----------------------------------------------------------------------

    def add_to_dnca(self, phone_numbers: List[str]) -> Dict[str, Any]:
        """
        Add phone numbers to Do Not Call Again list.

        Args:
            phone_numbers: List of E.164 phone numbers (max 50 per request)

        Returns:
            API response with success/failure info
        """
        if len(phone_numbers) > 50:
            raise JustCallError("Maximum 50 phone numbers per DNCA request")

        return self._jc_v1_request(
            "POST",
            "autodialer/contacts/bulk-dnca",
            json={"phone_numbers": phone_numbers},
        )

    def add_to_dnca_batched(self, phone_numbers: List[str]) -> Dict[str, int]:
        """
        Add phone numbers to DNCA in batches of 50.

        Returns:
            {"success_count": N, "failed_count": M}
        """
        success_count = 0
        failed_count = 0

        for i in range(0, len(phone_numbers), 50):
            batch = phone_numbers[i : i + 50]
            try:
                result = self.add_to_dnca(batch)
                success_count += result.get("success_count", len(batch))
                failed_count += result.get("failed_count", 0)
            except JustCallError:
                failed_count += len(batch)

        return {"success_count": success_count, "failed_count": failed_count}

    # -----------------------------------------------------------------------
    # Sales Dialer: Campaign Contacts
    # -----------------------------------------------------------------------

    def list_campaign_contacts(
        self,
        campaign_id: str,
        page: int = 1,
        per_page: int = 50,
    ) -> Dict[str, Any]:
        """List contacts in a campaign."""
        return self._jc_v1_request(
            "POST",
            "autodialer/campaigns/campaign-contacts",
            json={"campaign_id": campaign_id, "page": page, "per_page": per_page},
        )

    def remove_contact_from_campaign(
        self,
        campaign_id: str,
        contact_id: str,
    ) -> Dict[str, Any]:
        """Remove a contact from a campaign."""
        data = self._jc_v1_request(
            "DELETE",
            f"autodialer/campaigns/{campaign_id}/contacts/{contact_id}",
        )
        return data or {"status": "removed"}
