from __future__ import annotations
import json
import os
import secrets
import hmac
import hashlib
from dataclasses import dataclass
from typing import Any, Optional

@dataclass(frozen=True)
class RoutedButton:
    ver: int
    game: str
    sid: str
    uid: int | None
    act: str
    payload: dict[str, Any]

class ButtonRouter:
    """
    Signed custom_id to prevent spoofing.
    Format (short-ish):
      v1|g=...|s=...|u=...|a=...|p=<json>|sig=<hex>
    """
    VERSION = 1

    def __init__(self, secret: Optional[str] = None):
        self.secret = (secret or os.getenv("VISION_SECRET") or "").encode("utf-8")

    @staticmethod
    def new_session_id() -> str:
        return secrets.token_urlsafe(8)

    def _sign(self, base: str) -> str:
        if not self.secret:
            # still works, just unsigned
            return ""
        return hmac.new(self.secret, base.encode("utf-8"), hashlib.sha256).hexdigest()[:16]

    def encode(self, game: str, sid: str, act: str, payload: dict[str, Any] | None = None, uid: int | None = None) -> str:
        payload = payload or {}
        p = json.dumps(payload, separators=(",", ":"), ensure_ascii=False)
        u = "" if uid is None else str(uid)
        base = f"v{self.VERSION}|g={game}|s={sid}|u={u}|a={act}|p={p}"
        sig = self._sign(base)
        return base + (f"|sig={sig}" if sig else "")

    def decode(self, custom_id: str) -> Optional[RoutedButton]:
        try:
            parts = custom_id.split("|")
            if not parts or not parts[0].startswith("v"):
                return None
            ver = int(parts[0][1:])
            kv = {}
            for item in parts[1:]:
                if "=" not in item:
                    continue
                k, v = item.split("=", 1)
                kv[k] = v

            game = kv.get("g", "")
            sid = kv.get("s", "")
            act = kv.get("a", "")
            uid_raw = kv.get("u", "")
            uid = int(uid_raw) if uid_raw.strip() else None
            payload = json.loads(kv.get("p", "{}"))

            if not game or not sid or not act:
                return None

            # verify signature if configured
            sig = kv.get("sig", "")
            if self.secret and sig:
                base = custom_id.split("|sig=")[0]
                if not hmac.compare_digest(sig, self._sign(base)):
                    return None

            return RoutedButton(ver=ver, game=game, sid=sid, uid=uid, act=act, payload=payload)
        except Exception:
            return None