"""WABridges + Claude: a WhatsApp bot in one file.

Webhook in -> Claude -> POST /send/text out. Run it against the sandbox first
(no phone needed), then point CUSTOMER_REF at a real bridge.
"""
import hashlib
import hmac
import os
import re
import threading
import time
from collections import OrderedDict, defaultdict, deque

import anthropic
import requests
from flask import Flask, request

WA_API_KEY = os.environ["WA_API_KEY"]
WA_WEBHOOK_SECRET = os.environ["WA_WEBHOOK_SECRET"]
CUSTOMER_REF = os.environ.get("CUSTOMER_REF", "sandbox")
BASE = f"https://wabridges.com/api/instances/{CUSTOMER_REF}/proxy"

# Customize me.
SYSTEM_PROMPT = """You are a friendly assistant answering on WhatsApp.
Keep replies short (1-3 sentences), plain text, no markdown."""

client = anthropic.Anthropic()  # reads ANTHROPIC_API_KEY
history = defaultdict(lambda: deque(maxlen=20))  # chat_id -> last 10 exchanges
seen = OrderedDict()  # X-Webhook-Event-Id dedupe: delivery is at-least-once
app = Flask(__name__)


def verify_webhook() -> bool:
    """Same check as https://wabridges.com/docs/webhooks#verify"""
    m = re.fullmatch(r"t=(\d+),v1=([0-9a-f]+)", request.headers.get("X-Webhook-Signature", ""))
    if not m:
        return False
    t, v1 = m.groups()
    if abs(time.time() - int(t)) > 300:  # stale
        return False
    expected = hmac.new(WA_WEBHOOK_SECRET.encode(), f"{t}.".encode() + request.get_data(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(v1, expected)


@app.post("/hook")
def hook():
    if not verify_webhook():
        return "", 401
    ev = request.get_json(silent=True) or {}
    event_id = request.headers.get("X-Webhook-Event-Id", "")
    if ev.get("event") != "message" or ev.get("from_me") or ev.get("is_group") or ev.get("type") != "text":
        return "", 200
    if event_id in seen:
        return "", 200
    seen[event_id] = True
    if len(seen) > 1000:
        seen.popitem(last=False)
    # Ack now: the bridge retries anything slower than 10s. Reply in the background.
    threading.Thread(target=reply, args=(ev["chat_id"], ev["body"], event_id), daemon=True).start()
    return "", 200


def reply(chat: str, text: str, event_id: str) -> None:
    msgs = history[chat]
    msgs.append({"role": "user", "content": text})
    try:
        r = client.messages.create(
            model="claude-sonnet-5",
            max_tokens=1024,                   # WhatsApp replies are short on purpose
            output_config={"effort": "low"},   # chat: fast and cheap; raise for harder tasks
            system=SYSTEM_PROMPT,
            messages=list(msgs),
        )
        if r.stop_reason == "refusal":
            answer = "Sorry, I can't help with that one."
        else:
            answer = "\n".join(b.text for b in r.content if b.type == "text").strip()
    except anthropic.RateLimitError:
        answer = "I'm a bit busy, try again in a minute."
    except anthropic.APIError as e:
        print("claude:", e)
        answer = "Something went wrong on my side."
    answer = answer or "..."
    msgs.append({"role": "assistant", "content": answer})
    send_text(chat, answer, event_id)


def send_text(chat: str, body: str, idempotency_key: str, retried: bool = False) -> None:
    res = requests.post(
        f"{BASE}/send/text",
        json={"chat": chat, "body": body},
        headers={"Authorization": f"Bearer {WA_API_KEY}", "Idempotency-Key": idempotency_key},
        timeout=30,
    )
    if res.status_code == 503 and not retried:  # an idle bridge is waking up: honor Retry-After once
        time.sleep(int(res.headers.get("Retry-After", "5")))
        return send_text(chat, body, idempotency_key, True)
    if not res.ok:
        print("send failed:", res.status_code, res.text)


if __name__ == "__main__":
    app.run(port=int(os.environ.get("PORT", "3000")))
