"""
Synchronous AgentPhone client.
"""

from __future__ import annotations

from typing import Optional

import requests

from ._http import DEFAULT_BASE_URL, _raise_for_status
from .models import (
    Agent,
    AgentList,
    Call,
    CallList,
    Contact,
    ContactList,
    Conversation,
    ConversationList,
    DailyUsage,
    MessageList,
    MonthlyUsage,
    PhoneNumber,
    PhoneNumberList,
    Usage,
    Webhook,
    WebhookDelivery,
    WebhookDeliveryStats,
)


class _Resource:
    def __init__(self, client: "AgentPhone") -> None:
        self._client = client

    def _get(self, path: str, **params) -> dict:
        return self._client._request("GET", path, params=params or None)

    def _post(self, path: str, **body) -> dict:
        return self._client._request("POST", path, json={k: v for k, v in body.items() if v is not None})

    def _patch(self, path: str, **body) -> dict:
        return self._client._request("PATCH", path, json={k: v for k, v in body.items() if v is not None})

    def _delete(self, path: str) -> dict:
        return self._client._request("DELETE", path)


class NumbersResource(_Resource):
    def list(self, limit: int = 20, offset: int = 0) -> PhoneNumberList:
        """List all phone numbers."""
        return PhoneNumberList.from_dict(self._get("/v1/numbers", limit=limit, offset=offset))

    def buy(self, country: str = "US", area_code: Optional[str] = None, agent_id: Optional[str] = None) -> PhoneNumber:
        """Purchase a new phone number. Optionally specify an area code (US/CA only)."""
        return PhoneNumber.from_dict(self._post("/v1/numbers", country=country, areaCode=area_code, agentId=agent_id))

    def release(self, number_id: str) -> dict:
        """Release a phone number (irreversible)."""
        return self._delete(f"/v1/numbers/{number_id}")

    def get_messages(
        self,
        number_id: str,
        limit: int = 50,
        before: Optional[str] = None,
        after: Optional[str] = None,
    ) -> MessageList:
        """Get SMS messages received on a number."""
        params: dict = {"limit": limit}
        if before:
            params["before"] = before
        if after:
            params["after"] = after
        return MessageList.from_dict(self._get(f"/v1/numbers/{number_id}/messages", **params))

    def list_calls(self, number_id: str, limit: int = 20, offset: int = 0) -> CallList:
        """List calls for a specific phone number."""
        return CallList.from_dict(self._get(f"/v1/numbers/{number_id}/calls", limit=limit, offset=offset))


class ContactsResource(_Resource):
    def list(self, limit: int = 50, offset: int = 0, search: Optional[str] = None) -> ContactList:
        """List contacts, optionally filtering by search term."""
        params: dict = {"limit": limit, "offset": offset}
        if search:
            params["search"] = search
        return ContactList.from_dict(self._get("/v1/contacts", **params))

    def create(
        self,
        phone_number: str,
        name: str,
        email: Optional[str] = None,
        notes: Optional[str] = None,
    ) -> Contact:
        """Create a new contact."""
        return Contact.from_dict(
            self._post("/v1/contacts", phoneNumber=phone_number, name=name, email=email, notes=notes)
        )

    def get(self, contact_id: str) -> Contact:
        """Get a contact by ID."""
        return Contact.from_dict(self._get(f"/v1/contacts/{contact_id}"))

    def update(
        self,
        contact_id: str,
        phone_number: Optional[str] = None,
        name: Optional[str] = None,
        email: Optional[str] = None,
        notes: Optional[str] = None,
    ) -> Contact:
        """Update a contact."""
        return Contact.from_dict(
            self._patch(f"/v1/contacts/{contact_id}", phoneNumber=phone_number, name=name, email=email, notes=notes)
        )

    def delete(self, contact_id: str) -> dict:
        """Delete a contact."""
        return self._delete(f"/v1/contacts/{contact_id}")


class AgentsResource(_Resource):
    def list(self, limit: int = 20, offset: int = 0) -> AgentList:
        """List all agents."""
        return AgentList.from_dict(self._get("/v1/agents", limit=limit, offset=offset))

    def create(
        self,
        name: str,
        description: Optional[str] = None,
        voice_mode: Optional[str] = None,
        system_prompt: Optional[str] = None,
        begin_message: Optional[str] = None,
        voice: Optional[str] = None,
        transfer_number: Optional[str] = None,
        voicemail_message: Optional[str] = None,
    ) -> Agent:
        """Create a new agent."""
        return Agent.from_dict(
            self._post(
                "/v1/agents",
                name=name,
                description=description,
                voiceMode=voice_mode,
                systemPrompt=system_prompt,
                beginMessage=begin_message,
                voice=voice,
                transferNumber=transfer_number,
                voicemailMessage=voicemail_message,
            )
        )

    def get(self, agent_id: str) -> Agent:
        """Get agent details including attached numbers."""
        return Agent.from_dict(self._get(f"/v1/agents/{agent_id}"))

    def update(
        self,
        agent_id: str,
        name: Optional[str] = None,
        description: Optional[str] = None,
        voice_mode: Optional[str] = None,
        system_prompt: Optional[str] = None,
        begin_message: Optional[str] = None,
        voice: Optional[str] = None,
        transfer_number: Optional[str] = None,
        voicemail_message: Optional[str] = None,
    ) -> Agent:
        """Update an agent."""
        return Agent.from_dict(
            self._patch(
                f"/v1/agents/{agent_id}",
                name=name,
                description=description,
                voiceMode=voice_mode,
                systemPrompt=system_prompt,
                beginMessage=begin_message,
                voice=voice,
                transferNumber=transfer_number,
                voicemailMessage=voicemail_message,
            )
        )

    def delete(self, agent_id: str) -> dict:
        """Delete an agent."""
        return self._delete(f"/v1/agents/{agent_id}")

    def attach_number(self, agent_id: str, number_id: str) -> dict:
        """Attach a phone number to an agent."""
        return self._post(f"/v1/agents/{agent_id}/numbers", numberId=number_id)

    def detach_number(self, agent_id: str, number_id: str) -> dict:
        """Detach a phone number from an agent. The number remains active and can be re-attached."""
        return self._delete(f"/v1/agents/{agent_id}/numbers/{number_id}")

    def list_calls(self, agent_id: str, limit: int = 20, offset: int = 0) -> CallList:
        """List calls for a specific agent."""
        return CallList.from_dict(self._get(f"/v1/agents/{agent_id}/calls", limit=limit, offset=offset))

    def list_conversations(self, agent_id: str, limit: int = 20, offset: int = 0) -> ConversationList:
        """List conversations for a specific agent."""
        return ConversationList.from_dict(self._get(f"/v1/agents/{agent_id}/conversations", limit=limit, offset=offset))

    def list_voices(self) -> list[dict]:
        """List available voices for agents."""
        data = self._get("/v1/agents/voices")
        return data.get("data", data if isinstance(data, list) else [])

    def set_webhook(self, agent_id: str, url: str, context_limit: Optional[int] = None, timeout: Optional[int] = None) -> Webhook:
        """Set the webhook for an agent."""
        return Webhook.from_dict(self._post(f"/v1/agents/{agent_id}/webhook", url=url, contextLimit=context_limit, timeout=timeout))

    def get_webhook(self, agent_id: str) -> Optional[Webhook]:
        """Get the webhook configured for an agent."""
        data = self._get(f"/v1/agents/{agent_id}/webhook")
        return Webhook.from_dict(data) if data else None

    def delete_webhook(self, agent_id: str) -> dict:
        """Delete the webhook for an agent."""
        return self._delete(f"/v1/agents/{agent_id}/webhook")

    def list_webhook_deliveries(self, agent_id: str, limit: int = 50) -> list[WebhookDelivery]:
        """List webhook delivery attempts for an agent."""
        data = self._get(f"/v1/agents/{agent_id}/webhook/deliveries", limit=limit)
        return [WebhookDelivery.from_dict(d) for d in data]

    def test_webhook(self, agent_id: str) -> dict:
        """Send a test event to the agent's webhook."""
        return self._post(f"/v1/agents/{agent_id}/webhook/test")


class CallsResource(_Resource):
    def list(
        self,
        limit: int = 20,
        offset: int = 0,
        status: Optional[str] = None,
        direction: Optional[str] = None,
        type: Optional[str] = None,
        search: Optional[str] = None,
    ) -> CallList:
        """List recent calls with optional filters."""
        params: dict = {"limit": limit, "offset": offset}
        if status:
            params["status"] = status
        if direction:
            params["direction"] = direction
        if type:
            params["type"] = type
        if search:
            params["search"] = search
        return CallList.from_dict(self._get("/v1/calls", **params))

    def get(self, call_id: str) -> Call:
        """Get call details and transcript."""
        return Call.from_dict(self._get(f"/v1/calls/{call_id}"))

    def make(
        self,
        agent_id: str,
        to_number: str,
        initial_greeting: Optional[str] = None,
        from_number_id: Optional[str] = None,
        voice: Optional[str] = None,
    ) -> Call:
        """
        Make an outbound call (webhook-based).

        The agent must have a webhook configured to handle the conversation.
        """
        return Call.from_dict(
            self._post(
                "/v1/calls",
                agentId=agent_id,
                toNumber=to_number,
                initialGreeting=initial_greeting,
                fromNumberId=from_number_id,
                voice=voice,
            )
        )

    def make_conversation(
        self,
        agent_id: str,
        to_number: str,
        topic: str,
        initial_greeting: Optional[str] = None,
        from_number_id: Optional[str] = None,
        voice: Optional[str] = None,
    ) -> Call:
        """
        Make an outbound call with a built-in LLM conversation.

        No webhook required. The AI handles the full conversation
        based on the topic/system prompt.
        """
        return Call.from_dict(
            self._post(
                "/v1/calls",
                agentId=agent_id,
                toNumber=to_number,
                systemPrompt=topic,
                initialGreeting=initial_greeting,
                fromNumberId=from_number_id,
                voice=voice,
            )
        )

    def create_web_call(self, agent_id: str, metadata: Optional[dict] = None) -> dict:
        """Create a web-based call."""
        return self._post("/v1/calls/web", agentId=agent_id, metadata=metadata)

    def get_transcript(self, call_id: str) -> dict:
        """Get the transcript for a call."""
        return self._get(f"/v1/calls/{call_id}/transcript")


class MessagesResource(_Resource):
    def send(
        self,
        agent_id: str,
        to_number: str,
        body: str,
        media_url: Optional[str] = None,
        number_id: Optional[str] = None,
    ) -> dict:
        """
        Send an outbound SMS or iMessage.

        Args:
            agent_id: Agent to send from.
            to_number: Destination phone number (any format, normalized to E.164).
            body: Message text.
            media_url: Optional media URL (MMS/iMessage only, requires Sendblue number).
            number_id: Optional phone number ID to send from (overrides agent lookup).
        """
        return self._post(
            "/v1/messages",
            agent_id=agent_id,
            to_number=to_number,
            body=body,
            media_url=media_url,
            number_id=number_id,
        )

    def react(self, message_id: str, reaction: str) -> dict:
        """
        Send a tapback reaction to a message (iMessage only).

        Args:
            message_id: The message ID to react to.
            reaction: One of: love, like, dislike, laugh, emphasize, question.
        """
        return self._post(f"/v1/messages/{message_id}/reactions", reaction=reaction)


class ConversationsResource(_Resource):
    def list(self, limit: int = 20, offset: int = 0) -> ConversationList:
        """List SMS conversations."""
        return ConversationList.from_dict(self._get("/v1/conversations", limit=limit, offset=offset))

    def get(self, conversation_id: str, message_limit: int = 50) -> Conversation:
        """Get a conversation with full message history."""
        return Conversation.from_dict(
            self._get(f"/v1/conversations/{conversation_id}", message_limit=message_limit)
        )

    def update(self, conversation_id: str, metadata: Optional[dict] = None) -> Conversation:
        """Update a conversation."""
        return Conversation.from_dict(self._patch(f"/v1/conversations/{conversation_id}", metadata=metadata))

    def get_messages(
        self,
        conversation_id: str,
        limit: int = 50,
        before: Optional[str] = None,
        after: Optional[str] = None,
    ) -> MessageList:
        """Get messages for a conversation with pagination."""
        params: dict = {"limit": limit}
        if before:
            params["before"] = before
        if after:
            params["after"] = after
        return MessageList.from_dict(self._get(f"/v1/conversations/{conversation_id}/messages", **params))


class WebhooksResource(_Resource):
    def get(self) -> Optional[Webhook]:
        """Get the configured webhook endpoint."""
        data = self._get("/v1/webhooks")
        return Webhook.from_dict(data) if data else None

    def set(self, url: str, context_limit: Optional[int] = None, timeout: Optional[int] = None) -> Webhook:
        """
        Register or update the webhook URL.

        Args:
            url: Publicly accessible HTTPS URL.
            context_limit: Number of recent messages to include in payloads (0-50).
            timeout: Webhook response timeout in seconds (5-120).
        """
        return Webhook.from_dict(self._post("/v1/webhooks", url=url, contextLimit=context_limit, timeout=timeout))

    def delete(self) -> dict:
        """Remove the webhook configuration."""
        return self._delete("/v1/webhooks")

    def list_deliveries(self, limit: int = 50) -> list[WebhookDelivery]:
        """Get recent webhook delivery attempts."""
        data = self._get("/v1/webhooks/deliveries", limit=limit)
        return [WebhookDelivery.from_dict(d) for d in data]

    def test(self) -> dict:
        """Send a test event to the configured webhook."""
        return self._post("/v1/webhooks/test")

    def get_delivery_stats(self, hours: int = 24) -> WebhookDeliveryStats:
        """Get webhook delivery statistics."""
        return WebhookDeliveryStats.from_dict(self._get("/v1/webhooks/deliveries/stats", hours=hours))

    def get_all_time_stats(self) -> dict:
        """Get all-time webhook delivery counts."""
        return self._get("/v1/webhooks/deliveries/all-time")


class UsageResource(_Resource):
    def get(self) -> Usage:
        """Get current usage summary."""
        return Usage.from_dict(self._get("/v1/usage"))

    def get_daily(self, days: int = 30) -> DailyUsage:
        """Get daily usage breakdown."""
        return DailyUsage.from_dict(self._get("/v1/usage/daily", days=days))

    def get_monthly(self, months: int = 6) -> MonthlyUsage:
        """Get monthly usage breakdown."""
        return MonthlyUsage.from_dict(self._get("/v1/usage/monthly", months=months))


class AgentPhone:
    """
    Synchronous AgentPhone client.

    Example::

        import os
        from agentphone import AgentPhone

        client = AgentPhone(api_key=os.environ["AGENTPHONE_API_KEY"])

        # Buy a number
        number = client.numbers.buy(country="US")

        # Make a conversation call
        call = client.calls.make_conversation(
            agent_id="...",
            to_number="+14155551234",
            topic="Schedule a dentist appointment for next Tuesday at 2pm",
        )
    """

    def __init__(
        self,
        api_key: str,
        base_url: str = DEFAULT_BASE_URL,
        timeout: float = 30.0,
    ) -> None:
        self.api_key = api_key
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout
        self._session = requests.Session()
        self._session.headers.update(
            {
                "Authorization": f"Bearer {api_key}",
                "Content-Type": "application/json",
                "User-Agent": "agentphone-python/0.4.0",
            }
        )

        self.numbers = NumbersResource(self)
        self.contacts = ContactsResource(self)
        self.agents = AgentsResource(self)
        self.calls = CallsResource(self)
        self.messages = MessagesResource(self)
        self.conversations = ConversationsResource(self)
        self.webhooks = WebhooksResource(self)
        self.usage = UsageResource(self)

    def _request(self, method: str, path: str, **kwargs) -> dict:
        url = f"{self.base_url}{path}"
        resp = self._session.request(method, url, timeout=self.timeout, **kwargs)

        if not resp.ok:
            _raise_for_status(resp.status_code, resp.text)

        if resp.status_code == 204:
            return {}
        return resp.json()

    def close(self) -> None:
        """Close the underlying HTTP session."""
        self._session.close()

    def __enter__(self) -> "AgentPhone":
        return self

    def __exit__(self, *args) -> None:
        self.close()
