import base64
import hashlib
import hmac
import time


def sign_callback(action: str, task_num: int, pr_num: int, expiry: int, secret: str) -> str:
    """callback_data 생성: '<action>:<task_num>:<pr_num>:<expiry>:<sig8>'

    action: 'a' (approve) | 'r' (reject) | 'd' (diff)
    sig8: base64url(HMAC-SHA256(secret, f'{action}:{task_num}:{pr_num}:{expiry}'))[:8]
    Telegram 64바이트 한도 고려.
    """
    payload = f"{action}:{task_num}:{pr_num}:{expiry}"
    mac = hmac.new(secret.encode("utf-8"), payload.encode("utf-8"), hashlib.sha256).digest()
    sig8 = base64.urlsafe_b64encode(mac).decode("ascii").rstrip("=")[:8]
    return f"{payload}:{sig8}"


def verify_callback(callback_data: str, secret: str, now: int | None = None) -> dict:
    """callback_data 검증.

    반환: {"ok": bool, "reason": str, "action": str, "task_num": int, "pr_num": int, "expiry": int}

    실패 사유:
    - "format" — 5필드 split 실패
    - "signature" — HMAC 불일치 (constant-time compare)
    - "expired" — expiry < now
    - "unknown_action" — action ∉ {a,r,d}
    """
    if now is None:
        now = int(time.time())
    parts = callback_data.split(":")
    if len(parts) != 5:
        return {"ok": False, "reason": "format"}
    action, task_str, pr_str, expiry_str, sig = parts
    if action not in ("a", "r", "d"):
        return {"ok": False, "reason": "unknown_action"}
    try:
        task_num = int(task_str)
        pr_num = int(pr_str)
        expiry = int(expiry_str)
    except ValueError:
        return {"ok": False, "reason": "format"}
    payload = f"{action}:{task_num}:{pr_num}:{expiry}"
    expected_mac = hmac.new(secret.encode("utf-8"), payload.encode("utf-8"), hashlib.sha256).digest()
    expected_sig = base64.urlsafe_b64encode(expected_mac).decode("ascii").rstrip("=")[:8]
    if not hmac.compare_digest(sig, expected_sig):
        return {"ok": False, "reason": "signature"}
    if expiry < now:
        return {"ok": False, "reason": "expired"}
    return {"ok": True, "reason": "", "action": action, "task_num": task_num, "pr_num": pr_num, "expiry": expiry}
