#!/usr/bin/env python3
"""Xipher Bot SDK — официальный Python-клиент Bot API (без зависимостей).

    from xipher_bot import XipherBot

    bot = XipherBot("123:AbC...")
    print(bot.get_me())

    for update in bot.listen():
        msg = update.get("message", {})
        if msg.get("text"):
            bot.send_message(msg["chat"]["id"], "echo: " + msg["text"])

API-референс: https://messenger.xipher.pro/developers
"""
from __future__ import annotations

import json
import time
import urllib.parse
import urllib.request
from typing import Any, Dict, Generator, Iterator, Optional

DEFAULT_BASE = "https://messenger.xipher.pro"


class XipherBotError(Exception):
    """Ошибка Bot API: code — error_code из ответа, description — текст."""

    def __init__(self, code: int, description: str):
        super().__init__(f"[{code}] {description}")
        self.code = code
        self.description = description


class XipherBot:
    def __init__(self, token: str, base_url: str = DEFAULT_BASE):
        if not token:
            raise ValueError("token required")
        self.token = token
        self.base = base_url.rstrip("/")

    # ---------- низкий уровень ----------
    def api(self, method: str, params: Optional[Dict[str, Any]] = None,
            http: str = "POST", timeout: int = 35) -> Dict[str, Any]:
        url = f"{self.base}/bot{self.token}/{method}"
        if http == "GET":
            qs = urllib.parse.urlencode(
                {k: v for k, v in (params or {}).items() if v is not None})
            req = urllib.request.Request(f"{url}?{qs}" if qs else url)
        else:
            req = urllib.request.Request(
                url, data=json.dumps(params or {}).encode("utf-8"),
                headers={"Content-Type": "application/json"})
        with urllib.request.urlopen(req, timeout=timeout) as resp:
            data = json.loads(resp.read().decode("utf-8"))
        if not data.get("ok"):
            raise XipherBotError(int(data.get("error_code", 0)),
                                 str(data.get("description", "unknown")))
        return data

    # ---------- методы ----------
    def get_me(self) -> Dict[str, Any]:
        return self.api("getMe", http="GET")

    def get_updates(self) -> list:
        """Новые апдейты. Сервер сам подтверждает выданное (fetch = consume)."""
        r = self.api("getUpdates", http="GET")
        return r.get("result", [])

    def send_message(self, chat_id: str, text: str, **extra) -> Dict[str, Any]:
        return self.api("sendMessage", {"chat_id": chat_id, "text": text, **extra})

    def reply(self, update: Dict[str, Any], text: str, **extra) -> Dict[str, Any]:
        msg = update.get("message", update)
        chat = (msg.get("chat") or {}).get("id") or (msg.get("from") or {}).get("id")
        if not chat:
            raise ValueError("reply(): no chat id in update")
        return self.send_message(chat, text, **extra)

    def edit_message_text(self, chat_id: str, message_id: str, text: str, **extra):
        return self.api("editMessageText", {
            "chat_id": chat_id, "message_id": message_id, "text": text, **extra})

    def delete_message(self, chat_id: str, message_id: str):
        return self.api("deleteMessage", {"chat_id": chat_id, "message_id": message_id})

    def forward_message(self, from_chat_id: str, message_id: str, chat_id: str):
        return self.api("forwardMessage", {
            "from_chat_id": from_chat_id, "message_id": message_id, "chat_id": chat_id})

    def send_photo(self, chat_id: str, url: str, **extra):
        return self.api("sendPhoto", {"chat_id": chat_id, "photo": url, **extra})

    def set_webhook(self, url: str):
        return self.api("setWebhook", {"url": url})

    def webhook_info(self):
        return self.api("getWebhookInfo", http="GET")

    # ---------- polling ----------
    def listen(self, poll_seconds: float = 1.5) -> Iterator[Dict[str, Any]]:
        """Бесконечный генератор апдейтов. Ошибки сети глотаются с паузой."""
        while True:
            try:
                yield from self.get_updates()
            except Exception:
                time.sleep(3.0)
            time.sleep(poll_seconds)


__all__ = ["XipherBot", "XipherBotError"]
