"""
zapapi.py - cliente Python da API de WhatsApp da PluguiZap (rotas /instances/{ID}/token/{TOKEN}).
Sem dependências: só a biblioteca padrão. Python 3.8+.

    from zapapi import ZapApi
    zap = ZapApi("SEU_ID", "SEU_TOKEN", "SEU_CLIENT_TOKEN")
    zap.texto("79999998888", "Seu pedido saiu para entrega")
    zap.arquivo("79999998888", "boleto.pdf", legenda="Segue o boleto")
"""
import base64
import json
import mimetypes
import os
import re
import urllib.error
import urllib.request

URL_PADRAO = "https://api.pluguizap.com.br"


class ZapErro(Exception):
    def __init__(self, http, resposta):
        self.http, self.resposta = http, resposta
        msg = resposta.get("error") if isinstance(resposta, dict) else str(resposta)
        super().__init__(f"HTTP {http}: {msg}")


class ZapApi:
    def __init__(self, instancia_id, token, client_token, url_base=URL_PADRAO, timeout=60, erro_como_excecao=False):
        self.base = f"{url_base.rstrip('/')}/instances/{instancia_id}/token/{token}"
        self.client_token, self.timeout, self.excecao = client_token, timeout, erro_como_excecao

    # ---------- base ----------
    @staticmethod
    def tel(t):
        t = str(t)
        if "-group" in t or "@" in t:
            return t
        d = re.sub(r"\D", "", t)
        return "55" + d if len(d) in (10, 11) else d

    def req(self, metodo, rota, corpo=None):
        """Chama qualquer rota. Devolve o JSON da resposta (dict). Erros vêm com 'error' (ou ZapErro, se erro_como_excecao)."""
        dados = json.dumps(corpo, ensure_ascii=False).encode("utf-8") if corpo is not None else None
        pedido = urllib.request.Request(f"{self.base}/{rota}", data=dados, method=metodo, headers={
            "Client-Token": self.client_token, "Content-Type": "application/json", "Accept": "application/json"})
        try:
            with urllib.request.urlopen(pedido, timeout=self.timeout) as r:
                return json.loads(r.read().decode("utf-8") or "{}")
        except urllib.error.HTTPError as e:
            try:
                resp = json.loads(e.read().decode("utf-8") or "{}")
            except ValueError:
                resp = {"error": f"HTTP {e.code}"}
            if self.excecao:
                raise ZapErro(e.code, resp)
            resp.setdefault("error", f"HTTP {e.code}")
            resp["http"] = e.code
            return resp
        except urllib.error.URLError as e:
            if self.excecao:
                raise ZapErro(0, {"error": str(e.reason)})
            return {"error": f"Falha de conexão: {e.reason}", "http": 0}

    # ---------- conexão ----------
    def status(self):
        return self.req("GET", "status")

    def conectado(self):
        return bool(self.status().get("connected"))

    def qr_code(self):
        """{'connected': False, 'value': 'data:image/png;base64,...'}"""
        return self.req("GET", "qr-code/image")

    def codigo_telefone(self, telefone):
        return self.req("GET", f"phone-code/{self.tel(telefone)}")

    def reiniciar(self):
        return self.req("GET", "restart")

    def desconectar(self):
        return self.req("GET", "disconnect")

    # ---------- mensagens ----------
    def texto(self, telefone, mensagem, digitando_seg=0):
        c = {"phone": self.tel(telefone), "message": mensagem}
        if digitando_seg:
            c["delayTyping"] = digitando_seg
        return self.req("POST", "send-text", c)

    def imagem(self, telefone, imagem, legenda=""):
        """imagem: link público ou data URL base64"""
        return self.req("POST", "send-image", {"phone": self.tel(telefone), "image": imagem, "caption": legenda})

    def documento(self, telefone, documento, nome_arquivo="arquivo.pdf", legenda=""):
        """documento: link público ou data URL base64"""
        ext = (os.path.splitext(nome_arquivo)[1].lstrip(".") or "pdf").lower()
        return self.req("POST", f"send-document/{ext}", {"phone": self.tel(telefone), "document": documento, "fileName": nome_arquivo, "caption": legenda})

    def arquivo(self, telefone, caminho, legenda=""):
        """Envia um arquivo do disco. Imagens vão como imagem; o resto como documento."""
        mime = mimetypes.guess_type(caminho)[0] or "application/octet-stream"
        with open(caminho, "rb") as f:
            dados = f"data:{mime};base64," + base64.b64encode(f.read()).decode()
        if mime.startswith("image/"):
            return self.imagem(telefone, dados, legenda)
        return self.documento(telefone, dados, os.path.basename(caminho), legenda)

    def enquete(self, telefone, pergunta, opcoes, uma_opcao=True):
        c = {"phone": self.tel(telefone), "message": pergunta, "poll": [{"name": str(o)} for o in opcoes]}
        if uma_opcao:
            c["pollMaxOptions"] = 1
        return self.req("POST", "send-poll", c)

    def botoes(self, telefone, mensagem, botoes):
        """Chega como menu numerado; a resposta volta no webhook em buttonsResponseMessage.buttonId ("1", "2"...)."""
        lista = [{"id": str(i + 1), "label": str(b)} for i, b in enumerate(botoes)]
        return self.req("POST", "send-button-list", {"phone": self.tel(telefone), "message": mensagem, "buttonList": {"buttons": lista}})

    def localizacao(self, telefone, latitude, longitude, nome="", endereco=""):
        return self.req("POST", "send-location", {"phone": self.tel(telefone), "latitude": latitude, "longitude": longitude, "title": nome, "address": endereco})

    def marcar_lida(self, telefone, message_id):
        return self.req("POST", "read-message", {"phone": self.tel(telefone), "messageId": message_id})

    # ---------- contatos e webhook ----------
    def tem_whatsapp(self, telefone):
        return bool(self.req("GET", f"phone-exists/{self.tel(telefone)}").get("exists"))

    def webhook_todos(self, url):
        return self.req("PUT", "update-every-webhooks", {"value": url})
