"""Server-side tool dispatcher; keep tool credentials behind this execution boundary.

Configure RELAYAI_API_KEY with the enterprise key bound to this Agent.
Only pass trusted, administrator-defined tool mappings to execute().
The dispatcher never retries a tool after consuming its ticket: the caller must
reconcile uncertain execution outcomes with the target system by request_id.
"""
import hashlib
import json
import os
import time
import urllib.request


class ApprovalPending(Exception):
    pass


def _post(path, payload):
    request = urllib.request.Request(
        'https://api.relayai.com.cn/v1/agent-actions/' + path,
        data=json.dumps(payload).encode('utf-8'),
        headers={'Authorization': 'Bearer ' + os.environ['RELAYAI_API_KEY'],
                 'Content-Type': 'application/json'}, method='POST',
    )
    with urllib.request.urlopen(request, timeout=20) as response:
        return json.load(response)


def execute(tools, tool_key, parameters, request_id, *, wait_seconds=0):
    """tools maps tool_key to (fixed action_type, callable).

    callable receives a frozen copy of parameters and request_id. Resume pending
    approval with exactly the same request_id and parameters. Never expose this
    function as an arbitrary Python/command executor to an untrusted client.
    """
    action_type, handler = tools[tool_key]
    frozen = json.dumps(parameters, sort_keys=True, separators=(',', ':'), ensure_ascii=False, allow_nan=False)
    payload = {'request_id': request_id, 'tool_key': tool_key, 'action_type': action_type,
               'parameters_hash': hashlib.sha256(frozen.encode('utf-8')).hexdigest()}
    deadline = time.monotonic() + max(0, wait_seconds)
    while True:
        authorization = _post('authorize', payload)
        if authorization['decision'] == 'deny':
            raise PermissionError(authorization.get('reason', 'Tool denied'))
        if authorization['decision'] == 'allow':
            ticket = authorization.get('capability_ticket')
            if not ticket:
                raise RuntimeError('Ticket already consumed or revoked; reconcile the original execution')
            break
        if time.monotonic() >= deadline:
            raise ApprovalPending('Resume after approval using the same request_id and parameters')
        time.sleep(5)
    consumed = _post('consume', {**payload, 'ticket': ticket})
    if not consumed.get('authorized') or consumed.get('parameters_hash') != payload['parameters_hash']:
        raise PermissionError('Execution parameters were not authorized')
    return handler(json.loads(frozen), request_id)
