"""Async JustCall adapter for Sales Dialer operations."""

from __future__ import annotations

import asyncio
import random
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional

import httpx


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


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


class JustCallRateLimitError(JustCallError):
    """Rate limit exceeded."""


@dataclass
class CampaignContact:
    phone: str
    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]:
        payload: Dict[str, Any] = {"phone": self.phone}
        if self.first_name:
            payload["first_name"] = self.first_name
        if self.last_name:
            payload["last_name"] = self.last_name
        if self.email:
            payload["email"] = self.email
        if self.company:
            payload["company"] = self.company
        if self.custom_fields:
            payload["custom_fields"] = self.custom_fields
        return payload


class JustCallAdapter:
    """Async JustCall API client for Sales Dialer operations."""

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

    def __init__(
        self,
        api_key: str,
        api_secret: str,
        timeout: float = 30.0,
        max_attempts: int = 4,
        base_delay: float = 1.0,
        max_delay: float = 8.0,
    ) -> None:
        self.api_key = api_key
        self.api_secret = api_secret
        self._client = httpx.AsyncClient(timeout=timeout)
        self._max_attempts = max_attempts
        self._base_delay = base_delay
        self._max_delay = max_delay

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

    async def _request(
        self,
        method: str,
        path: str,
        *,
        params: Optional[Dict[str, Any]] = None,
        json: Optional[Dict[str, Any]] = None,
        base_url: Optional[str] = None,
    ) -> Dict[str, Any]:
        root = base_url or self.V1_BASE
        url = f"{root}/{path.lstrip('/')}"
        attempt = 0
        delay = self._base_delay

        while True:
            resp = await self._client.request(
                method,
                url,
                params=params,
                json=json,
                headers=self._headers(),
            )

            if resp.status_code in (401, 403):
                raise JustCallAuthError("Authentication failed")

            if resp.status_code == 429 or resp.status_code >= 500:
                if attempt >= self._max_attempts - 1:
                    if resp.status_code == 429:
                        raise JustCallRateLimitError("Rate limit exceeded")
                    raise JustCallError(f"JustCall API error {resp.status_code}")

                retry_after = resp.headers.get("Retry-After")
                if retry_after:
                    try:
                        delay = max(float(retry_after), self._base_delay)
                    except ValueError:
                        delay = self._base_delay
                jitter = random.uniform(0.5, 1.5)
                await asyncio.sleep(min(delay * jitter, self._max_delay))
                delay = min(delay * 2, self._max_delay)
                attempt += 1
                continue

            if resp.status_code >= 400:
                raise JustCallError(f"JustCall API error {resp.status_code}")

            if not resp.content:
                return {}
            try:
                return resp.json()
            except ValueError:
                return {}

    async def list_campaigns(self, status: Optional[str] = None) -> List[Dict[str, Any]]:
        """List Sales Dialer campaigns."""
        payload: Dict[str, Any] = {"page": 1, "per_page": 100}
        if status:
            payload["status"] = status
        data = await self._request("POST", "autodialer/campaigns/list", json=payload)
        items = data.get("data", []) if isinstance(data, dict) else []
        return [
            {
                "id": str(item.get("id", "")),
                "name": item.get("name", ""),
                "status": item.get("status"),
                "contact_count": item.get("contact_count", 0),
            }
            for item in items
        ]

    async def list_numbers(self) -> List[Dict[str, Any]]:
        """List JustCall numbers available for SMS/WhatsApp."""
        data = await self._request("GET", "numbers", base_url=self.V2_BASE)

        items: List[Dict[str, Any]] = []
        if isinstance(data, dict):
            for key in ("data", "numbers", "results"):
                if isinstance(data.get(key), list):
                    items = data.get(key) or []
                    break
        elif isinstance(data, list):
            items = data

        def _coerce_bool(value: Any) -> Optional[bool]:
            if value is None:
                return None
            if isinstance(value, bool):
                return value
            if isinstance(value, (int, float)):
                return bool(value)
            if isinstance(value, str):
                return value.strip().lower() in ("1", "true", "yes", "enabled")
            return None

        results: List[Dict[str, Any]] = []
        for item in items:
            if not isinstance(item, dict):
                continue
            owner = item.get("owner") if isinstance(item.get("owner"), dict) else None
            if owner is None and isinstance(item.get("user"), dict):
                owner = item.get("user")
            if owner is None and isinstance(item.get("agent"), dict):
                owner = item.get("agent")

            owner_id = (
                item.get("owner_id")
                or item.get("user_id")
                or (owner or {}).get("id")
                or (owner or {}).get("user_id")
            )
            owner_email = (
                item.get("owner_email")
                or item.get("user_email")
                or (owner or {}).get("email")
            )
            owner_name = (
                item.get("owner_name")
                or item.get("user_name")
                or (owner or {}).get("name")
                or (owner or {}).get("full_name")
            )
            number = (
                item.get("number")
                or item.get("justcall_number")
                or item.get("phone_number")
                or item.get("phone")
                or ""
            )
            label = (
                item.get("label")
                or item.get("name")
                or item.get("friendly_name")
                or item.get("display_name")
                or ""
            )
            sms_enabled = _coerce_bool(item.get("sms_enabled") or item.get("sms"))
            whatsapp_enabled = _coerce_bool(
                item.get("whatsapp_enabled") or item.get("whatsapp")
            )

            capabilities = item.get("capabilities")
            if isinstance(capabilities, list):
                cap_set = {str(c).lower() for c in capabilities}
                if sms_enabled is None:
                    sms_enabled = "sms" in cap_set or "text" in cap_set
                if whatsapp_enabled is None:
                    whatsapp_enabled = "whatsapp" in cap_set

            results.append({
                "id": str(item.get("id") or item.get("number_id") or number),
                "number": str(number),
                "label": str(label),
                "sms_enabled": sms_enabled,
                "whatsapp_enabled": whatsapp_enabled,
                "owner_id": str(owner_id) if owner_id is not None else None,
                "owner_email": str(owner_email) if owner_email is not None else None,
                "owner_name": str(owner_name) if owner_name is not None else None,
            })

        return results

    async def send_sms(
        self,
        from_number: str,
        to_number: str,
        body: str,
    ) -> Dict[str, Any]:
        """Send a single SMS via JustCall."""
        payload = {
            "justcall_number": from_number,
            "contact_number": to_number,
            "body": body,
        }
        return await self._request(
            "POST",
            "texts/new",
            json=payload,
            base_url=self.V2_BASE,
        )

    async def send_whatsapp(
        self,
        from_number: str,
        to_number: str,
        body: Optional[str] = None,
        template_id: Optional[int] = None,
    ) -> Dict[str, Any]:
        """Send a single WhatsApp message via JustCall."""
        payload: Dict[str, Any] = {
            "justcall_number": from_number,
            "contact_number": to_number,
        }
        if body:
            payload["body"] = body
        if template_id is not None:
            payload["template_id"] = template_id
        return await self._request(
            "POST",
            "whatsapp/messages/send",
            json=payload,
            base_url=self.V2_BASE,
        )

    async def get_campaign(self, campaign_id: str) -> Dict[str, Any]:
        """Fetch a single campaign payload."""
        return await self._request("GET", f"autodialer/campaigns/{campaign_id}")

    async def bulk_import_contacts(
        self,
        campaign_id: str,
        contacts: List[CampaignContact],
    ) -> Dict[str, Any]:
        """Import contacts in a single batch (max 250)."""
        if len(contacts) > self.MAX_BATCH_SIZE:
            raise JustCallError("Maximum 250 contacts per bulk import request")

        payload = {
            "campaign_id": campaign_id,
            "contacts": [contact.to_api_dict() for contact in contacts],
        }
        data = await self._request(
            "POST",
            "autodialer/contacts/bulk-import",
            json=payload,
        )
        return {
            "success_count": int(data.get("success_count", len(contacts))) if isinstance(data, dict) else len(contacts),
            "failed_count": int(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 [],
            "raw": data,
        }

    async def close(self) -> None:
        await self._client.aclose()
