"""Speakvora Python SDK (single file, standard library only). Keep your API key on a server.

    from speakvora import Speakvora
    sv = Speakvora(os.environ["SPEAKVORA_KEY"])
    clip = sv.speak("Your order has shipped.", voice="af_heart")      # {"url": ..., "voice": ..., "lang": ..., "characters": ...}
    sv.save("Welcome back.", "welcome.mp3")                            # downloads the audio file
    ok = verify_webhook(secret, raw_body_bytes, headers)               # check a webhook signature
"""
import hashlib, hmac, json, time, urllib.request, urllib.error


class SpeakvoraError(Exception):
    def __init__(self, status, body):
        super().__init__(body.get("error", f"http_{status}") if isinstance(body, dict) else f"http_{status}")
        self.status, self.body = status, body


class Speakvora:
    def __init__(self, key, base="https://dev.speakvora.com/api"):
        if not key:
            raise ValueError("Speakvora: API key required")
        self.key, self.base = key, base.rstrip("/")

    def _req(self, path, data=None):
        req = urllib.request.Request(self.base + path, data=json.dumps(data).encode() if data is not None else None, method="POST" if data is not None else "GET",
                                     headers={"content-type": "application/json", "x-api-key": self.key})
        try:
            with urllib.request.urlopen(req, timeout=30) as r:
                return json.loads(r.read())
        except urllib.error.HTTPError as e:
            raise SpeakvoraError(e.code, json.loads(e.read() or b"{}"))

    def voices(self):
        return self._req("/v1/voices")

    def speak(self, text, voice="af_heart", lang="en"):
        return self._req("/v1/speak", {"text": text, "voice": voice, "lang": lang})

    def save(self, text, path, **kw):
        clip = self.speak(text, **kw)
        with urllib.request.urlopen(clip["url"], timeout=30) as r, open(path, "wb") as f:
            f.write(r.read())
        return clip


def verify_webhook(secret, raw_body, headers, tolerance=300):
    h = {k.lower(): v for k, v in headers.items()}
    ts, sig = h.get("x-speakvora-timestamp"), h.get("x-speakvora-signature")
    if not ts or not sig or abs(time.time() - int(ts)) > tolerance:
        return False
    body = raw_body if isinstance(raw_body, bytes) else raw_body.encode()
    return hmac.compare_digest(hmac.new(secret.encode(), ts.encode() + b"." + body, hashlib.sha256).hexdigest(), sig)
