"""Dependency-free ActShield HTTP integration. Works with any model/framework.

Agent gets Client; a SEPARATE trusted process gets Executor + business credentials.
Wrapping a callable in the Agent process alone is cooperative, not bypass-proof.
"""
import hashlib
import ipaddress
import json
import urllib.error
import urllib.request
from urllib.parse import urlsplit

def canonical(value):return json.dumps(value,sort_keys=True,separators=(',',':'),ensure_ascii=False,allow_nan=False)
class ActShieldError(Exception):pass
class ApprovalRequired(ActShieldError):
    def __init__(self,decision):self.decision=decision;super().__init__('Independent approval required: '+decision['id'])
class Blocked(ActShieldError):
    def __init__(self,decision):self.decision=decision;super().__init__('Action blocked: '+decision['id'])
class OutcomeUnconfirmed(ActShieldError):pass

class HTTP:
    def __init__(self,base_url,token,allow_local_http=False):
        u=urlsplit(base_url)
        local=False
        try:local=ipaddress.ip_address(u.hostname or '').is_loopback
        except ValueError:local=u.hostname=='localhost'
        if u.scheme!='https' and not (u.scheme=='http' and local and allow_local_http):raise ValueError('HTTPS required (explicit local HTTP only for tests)')
        if u.username or u.password or u.query or u.fragment:raise ValueError('Invalid base URL')
        self.base=base_url.rstrip('/');self.token=token
    def request(self,method,path,body=None):
        data=canonical(body).encode() if body is not None else None
        req=urllib.request.Request(self.base+path,data=data,method=method,headers={'Authorization':'Bearer '+self.token,'Content-Type':'application/json'})
        class NoRedirect(urllib.request.HTTPRedirectHandler):
            def redirect_request(self,*args,**kwargs):return None
        try:
            with urllib.request.build_opener(NoRedirect).open(req,timeout=100) as r:return json.load(r)
        except urllib.error.HTTPError as e:raise ActShieldError('Platform rejected request (HTTP '+str(e.code)+'); do not execute.') from None
        except (urllib.error.URLError,TimeoutError,ValueError):raise ActShieldError('Platform unavailable; do not execute. Retry evaluation with the same event_id.') from None

class Client:
    def __init__(self,base_url,agent_token,*,allow_local_http=False,transport=None):
        self.http=transport or HTTP(base_url,agent_token,allow_local_http)
    def event(self,*,event_id,run_id,kind,content='',purpose='',tool=None,arguments=None,reported_cost_usd=0):
        data={'event_id':event_id,'run_id':run_id,'kind':kind,'content':content,'purpose':purpose,'arguments':arguments or {},'reported_cost_usd':reported_cost_usd}
        if tool is not None:data['tool']=tool
        return self.http.request('POST','/v1/runtime/events',data)
    def prepare(self,*,event_id,run_id,tool,arguments,purpose=''):
        d=self.event(event_id=event_id,run_id=run_id,kind='tool_call',tool=tool,arguments=arguments,purpose=purpose)
        return self.resume(d['id'],event_id=event_id,run_id=run_id,tool=tool,arguments=arguments)
    def resume(self,decision_id,*,event_id,run_id,tool,arguments):
        d=self.http.request('GET','/v1/runtime/decisions/'+decision_id)
        if d['status'] in ('pending_approval','review_required'):raise ApprovalRequired(d)
        if d['status'] not in ('allowed','approved'):raise Blocked(d)
        p=self.http.request('POST','/v1/runtime/decisions/'+decision_id+'/permit',{})
        return {'permit':p['permit'],'event_id':event_id,'run_id':run_id,'tool':tool,'arguments':arguments}
    @staticmethod
    def require_safe_context(decision):
        if decision['status']=='observed':raise ActShieldError('Observe-only mode cannot authorize execution.')
        if decision['recommendation']=='deny':raise Blocked(decision)
        if decision['recommendation']=='require_approval':raise ApprovalRequired(decision)

class Executor:
    def __init__(self,base_url,executor_token,tools,*,allow_local_http=False,transport=None):
        self.http=transport or HTTP(base_url,executor_token,allow_local_http);self.tools=dict(tools)
    def invoke(self,request):
        if request.get('tool') not in self.tools:raise ActShieldError('Tool is not installed in this executor.')
        approved=self.http.request('POST','/v1/executor/consume',request)
        if approved.get('authorized') is not True:raise ActShieldError('Execution not authorized.')
        try:
            result=self.tools[request['tool']](**request['arguments'])
            encoded=canonical(result);status='succeeded';summary='Business callable returned successfully.'
        except Exception:
            # An exception is not proof that no side effect occurred. Never automatically retry.
            result=None;encoded='null';status='unknown';summary='Business callable raised an exception; reconcile the business system before retrying.'
        receipt={'decision_id':approved['decision_id'],'status':status,'result_digest':hashlib.sha256(encoded.encode()).hexdigest(),'summary':summary}
        try:self.http.request('POST','/v1/executor/receipt',receipt)
        except ActShieldError:raise OutcomeUnconfirmed('Permit was consumed; receipt could not be confirmed. Inspect business state; do not invoke again.') from None
        if status!='succeeded':raise OutcomeUnconfirmed(summary)
        return {'decision_id':approved['decision_id'],'result':result,'receipt':receipt}
