"""Signal channel implementation via signal-cli REST API.

Requires signal-cli-rest-api running locally or remotely.
See: https://github.com/bbernhard/signal-cli-rest-api

Configuration:
    signal_api_url = "http://localhost:8080"  # signal-cli REST API
    signal_number = "+1234567890"              # registered Signal number
"""

from __future__ import annotations

import asyncio
import logging
from collections.abc import Callable
from typing import Any

import httpx

from agntspace.channels.base import Channel, ChannelMessage

log = logging.getLogger(__name__)


class SignalChannel(Channel):
    """Signal messaging channel via signal-cli REST API."""

    name = "signal"

    def __init__(self, api_url: str, number: str) -> None:
        self._api_url = api_url.rstrip("/")
        self._number = number
        self._connected = False
        self._message_callback: Callable | None = None
        self._chat_id: str | None = None
        self._poll_task: asyncio.Task | None = None
        self._client: httpx.AsyncClient | None = None

    # ── lifecycle ────────────────────────────────────────────────────

    async def connect(self) -> None:
        """Verify signal-cli API is reachable and number is registered."""
        self._client = httpx.AsyncClient(timeout=30.0)

        resp = await self._client.get(f"{self._api_url}/v1/about")
        if resp.status_code != 200:
            raise ConnectionError(f"Signal API unreachable ({resp.status_code})")

        # Check the number is registered
        resp = await self._client.get(f"{self._api_url}/v1/accounts")
        accounts = resp.json() if resp.status_code == 200 else []
        numbers = [a if isinstance(a, str) else a.get("number", "") for a in accounts]
        if self._number not in numbers:
            log.warning("Signal number %s not in registered accounts: %s", self._number, numbers)

        self._connected = True
        log.info("Signal connected: %s via %s", self._number, self._api_url)

        # Start polling for messages
        self._poll_task = asyncio.create_task(self._poll_loop())

    async def disconnect(self) -> None:
        self._connected = False
        if self._poll_task and not self._poll_task.done():
            self._poll_task.cancel()
            try:
                await self._poll_task
            except asyncio.CancelledError:
                pass
        if self._client:
            await self._client.aclose()
            self._client = None
        log.info("Signal disconnected")

    @property
    def is_connected(self) -> bool:
        return self._connected

    # ── sending ──────────────────────────────────────────────────────

    async def send_message(self, chat_id: str, text: str) -> None:
        """Send a message to a Signal number or group."""
        if not self._client:
            return
        body: dict[str, Any] = {
            "message": text,
            "number": self._number,
        }
        # chat_id can be a phone number or group ID
        if chat_id.startswith("+"):
            body["recipients"] = [chat_id]
        else:
            body["recipients"] = [chat_id]

        resp = await self._client.post(f"{self._api_url}/v2/send", json=body)
        if resp.status_code not in (200, 201):
            log.warning("Signal send failed (%d): %s", resp.status_code, resp.text[:200])

    async def send_notification(self, chat_id: str, text: str) -> None:
        await self.send_message(chat_id, f"🔔 {text}")

    # ── polling for incoming messages ────────────────────────────────

    async def _poll_loop(self) -> None:
        """Poll signal-cli REST API for new messages."""
        while self._connected:
            try:
                resp = await self._client.get(  # type: ignore[union-attr]
                    f"{self._api_url}/v1/receive/{self._number}",
                    timeout=35.0,
                )
                if resp.status_code != 200:
                    await asyncio.sleep(5)
                    continue

                messages = resp.json()
                if not isinstance(messages, list):
                    messages = []

                for msg in messages:
                    await self._handle_message(msg)

            except asyncio.CancelledError:
                break
            except httpx.ReadTimeout:
                continue
            except Exception as e:
                log.warning("Signal poll error: %s", e)
                await asyncio.sleep(5)

    async def _handle_message(self, msg: dict) -> None:
        """Process a single incoming Signal message."""
        envelope = msg.get("envelope", msg)
        data_msg = envelope.get("dataMessage")
        if not data_msg:
            return

        text = data_msg.get("message", "")
        if not text:
            return

        sender = envelope.get("source", "")
        # Skip own messages
        if sender == self._number:
            return

        group_info = data_msg.get("groupInfo")
        chat_id = group_info.get("groupId", sender) if group_info else sender
        self._chat_id = chat_id

        channel_msg = ChannelMessage(
            channel="signal",
            sender=sender,
            text=text,
            metadata={
                "chat_id": chat_id,
                "sender_id": sender,
                "timestamp": str(envelope.get("timestamp", "")),
            },
        )

        if self._message_callback:
            try:
                response = await self._message_callback(channel_msg)
                if response:
                    await self.send_message(chat_id, response)
            except Exception as e:
                log.error("Signal message handler error: %s", e)

    # ── webhook (alternative to polling) ─────────────────────────────

    async def handle_webhook(self, payload: dict[str, Any]) -> str | None:
        """Process an incoming webhook from signal-cli."""
        await self._handle_message(payload)
        return None
