"""
Macaroon implementation for L402 discourse tolls.

HMAC-SHA256 based with caveats: expiry, endpoint, method, context, agent.
"""

from __future__ import annotations

from typing import List

import base64
import hashlib
import hmac
import json
import time
from typing import Any, Optional


def _hmac_sha256(key: str | bytes, data: str) -> str:
    """HMAC-SHA256 helper. Key can be hex string or bytes."""
    if isinstance(key, str):
        try:
            key_bytes = bytes.fromhex(key)
        except ValueError:
            key_bytes = key.encode("utf-8")
    else:
        key_bytes = key
    return hmac.new(key_bytes, data.encode("utf-8"), hashlib.sha256).hexdigest()


def create_macaroon(
    secret: str,
    payment_hash: str,
    *,
    expires_at: Optional[int] = None,
    endpoint: Optional[str] = None,
    method: Optional[str] = None,
    context_id: Optional[str] = None,
    agent_id: Optional[str] = None,
    max_actions: Optional[int] = None,
) -> dict:
    """Create a macaroon with caveats."""
    caveats: list[str] = []

    if expires_at is not None:
        caveats.append(f"expires_at = {expires_at}")
    if endpoint is not None:
        caveats.append(f"endpoint = {endpoint}")
    if method is not None:
        caveats.append(f"method = {method}")
    if context_id is not None:
        caveats.append(f"context = {context_id}")
    if agent_id is not None:
        caveats.append(f"agent = {agent_id}")
    if max_actions is not None:
        caveats.append(f"max_actions = {max_actions}")

    # Chained HMAC signature
    sig = _hmac_sha256(secret, payment_hash)
    for caveat in caveats:
        sig = _hmac_sha256(sig, caveat)

    return {"id": payment_hash, "caveats": caveats, "signature": sig}


def verify_macaroon(
    secret: str,
    macaroon: dict,
    *,
    endpoint: Optional[str] = None,
    method: Optional[str] = None,
    context_id: Optional[str] = None,
    agent_id: Optional[str] = None,
) -> tuple[bool, Optional[str]]:
    """
    Verify a macaroon's signature and caveats.
    Returns (valid, error_message).
    """
    # Recompute signature
    sig = _hmac_sha256(secret, macaroon["id"])
    for caveat in macaroon["caveats"]:
        sig = _hmac_sha256(sig, caveat)

    if sig != macaroon["signature"]:
        return False, "Invalid signature"

    # Validate caveats
    for caveat in macaroon["caveats"]:
        key, _, value = caveat.partition(" = ")

        if key == "expires_at":
            if time.time() > int(value):
                return False, "Macaroon expired"

        elif key == "endpoint":
            if endpoint and endpoint != value:
                return False, f"Endpoint mismatch: expected {value}"

        elif key == "method":
            if method and method.upper() != value.upper():
                return False, f"Method mismatch: expected {value}"

        elif key == "context":
            if context_id and context_id != value:
                return False, f"Context mismatch: expected {value}"

        elif key == "agent":
            if agent_id and agent_id != value:
                return False, f"Agent mismatch: expected {value}"
        # Unknown caveats are ignored (forward-compatible)

    return True, None


def encode_macaroon(macaroon: dict) -> str:
    """Encode macaroon to base64."""
    return base64.b64encode(json.dumps(macaroon).encode()).decode()


def decode_macaroon(encoded: str) -> Optional[dict]:
    """Decode macaroon from base64."""
    try:
        return json.loads(base64.b64decode(encoded))
    except Exception:
        return None


def verify_preimage(preimage: str, payment_hash: str) -> bool:
    """Verify that SHA256(preimage) == payment_hash."""
    computed = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
    return computed == payment_hash
