from __future__ import annotations

import json
from typing import Any, Mapping, Optional
from urllib import error, parse, request

from armorer.messaging.gateway import MessagingGateway
from armorer.messaging.models import MessagingEvent, UserMapping


class SignalGateway(MessagingGateway):
    """Signal adapter via signal-cli-rest-api."""

    provider = "signal"

    def __init__(self, endpoint: Optional[str] = None, account: Optional[str] = None, timeout: int = 30):
        self.endpoint = (endpoint or "http://127.0.0.1:8085").rstrip("/")
        self.account = account
        self.timeout = timeout

    def _url(self, path: str) -> str:
        return f"{self.endpoint}{path}"

    def _request(self, method: str, path: str, payload: Optional[Mapping[str, Any]] = None) -> Any:
        body = None
        headers: dict[str, str] = {}
        if payload is not None:
            body = json.dumps(dict(payload)).encode("utf-8")
            headers["Content-Type"] = "application/json"

        req = request.Request(self._url(path), data=body, headers=headers, method=method.upper())
        try:
            with request.urlopen(req, timeout=self.timeout) as resp:
                raw = resp.read().decode("utf-8")
        except error.HTTPError as exc:
            err_body = exc.read().decode("utf-8", errors="ignore")
            raise RuntimeError(f"Signal API HTTP {exc.code}: {err_body}") from exc
        except error.URLError as exc:
            raise RuntimeError(f"Signal API connection error: {exc.reason}") from exc

        if not raw:
            return {}
        try:
            return json.loads(raw)
        except Exception:
            return {"raw": raw}

    def health(self) -> bool:
        try:
            self._request("GET", "/v1/about")
            return True
        except Exception:
            return False

    def register(self, number: str, use_voice: bool = False, captcha: Optional[str] = None) -> Any:
        params = {}
        if use_voice:
            params["voice"] = "true"
        if captcha:
            params["captcha"] = captcha
        suffix = f"?{parse.urlencode(params)}" if params else ""
        return self._request("POST", f"/v1/register/{parse.quote(number, safe='')}"+suffix)

    def verify(self, number: str, code: str, pin: Optional[str] = None) -> Any:
        path = f"/v1/register/{parse.quote(number, safe='')}/verify/{parse.quote(code, safe='')}"
        if pin:
            path = f"{path}?pin={parse.quote(pin, safe='')}"
        return self._request("POST", path)

    def receive_messages(self, account: Optional[str] = None, timeout: int = 1, max_messages: int = 10) -> list[dict[str, Any]]:
        number = account or self.account
        if not number:
            raise ValueError("Signal account/number is required.")
        path = f"/v1/receive/{parse.quote(number, safe='')}?timeout={int(timeout)}&maxMessages={int(max_messages)}"
        data = self._request("GET", path)
        return data if isinstance(data, list) else []

    def send_message(self, channel_id: str, text: str, reply_to: Optional[str] = None) -> dict[str, Any]:
        number = self.account
        if not number:
            raise ValueError("Signal account/number is required for sending.")

        payload = {
            "number": number,
            "recipients": [channel_id],
            "message": text,
        }
        if reply_to:
            payload["quoteTimestamp"] = int(reply_to)

        out = self._request("POST", "/v2/send", payload=payload)
        return {"ok": True, "provider": self.provider, "raw": out}

    def normalize_event(self, payload: Mapping[str, Any]) -> Optional[MessagingEvent]:
        if not isinstance(payload, Mapping):  # pragma: no cover
            return None

        envelope = payload.get("envelope")
        if not isinstance(envelope, Mapping):
            return None

        source = envelope.get("sourceNumber") or envelope.get("source")
        ts = envelope.get("timestamp")
        data_message = envelope.get("dataMessage")
        if not isinstance(data_message, Mapping):
            return None
        text = data_message.get("message")
        if source is None or text is None:
            return None
        event_id = str(ts or data_message.get("timestamp") or "")

        return MessagingEvent(
            provider=self.provider,
            event_id=str(event_id),
            channel_id=str(source),
            user_id=str(source),
            text=str(text),
            timestamp=int(ts) if isinstance(ts, int) else None,
            message_id=str(data_message.get("timestamp")) if data_message.get("timestamp") is not None else None,
            raw=dict(payload),
        )

    def map_user(self, event: MessagingEvent) -> UserMapping:
        envelope = event.raw.get("envelope") if isinstance(event.raw, Mapping) else None
        source_name = envelope.get("sourceName") if isinstance(envelope, Mapping) else None
        return UserMapping(
            provider=self.provider,
            external_user_id=event.user_id,
            external_channel_id=event.channel_id,
            display_name=str(source_name) if source_name else None,
        )
