#!/usr/bin/env python3
"""LaunchCheck invoice client (Python 3 standard library).

Never holds a wallet key or sends funds. Your wallet component must authorize
and make the exact quoted transfer once. Keep the state file private.

python agent_client.py quote https://launchcheck-tn.oe-nonprofit.chatgpt.site https://your-site.com --authorized
python agent_client.py verify --tx 0x...
python agent_client.py get
"""
import argparse, json, os, secrets, sys, urllib.request, urllib.error
from pathlib import Path

def call(origin, path, key, payload=None, quote=False):
    headers={'Content-Type':'application/json','User-Agent':'LaunchCheckAgent/1.0'}
    headers['Idempotency-Key' if quote else 'Authorization']=key if quote else 'Bearer '+key
    req=urllib.request.Request(origin+path,headers=headers,data=json.dumps(payload).encode() if payload is not None else None)
    try:
        with urllib.request.urlopen(req,timeout=60) as res:
            status,raw=res.status,res.read()
    except urllib.error.HTTPError as e:
        status,raw=e.code,e.read()
    except urllib.error.URLError as e:
        raise SystemExit('Connection failed: '+str(e.reason)+'. Check your network and Python certificate store; do not disable TLS verification.')
    try:return status,json.loads(raw)
    except ValueError:raise SystemExit('The service returned non-JSON HTTP '+str(status)+'. Check the service origin and retry; no payment was sent.')

def save(path,data):
    fd=os.open(path,os.O_WRONLY|os.O_CREAT|os.O_TRUNC,0o600)
    with os.fdopen(fd,'w') as f:json.dump(data,f,indent=2)

p=argparse.ArgumentParser(description=__doc__,formatter_class=argparse.RawDescriptionHelpFormatter)
p.add_argument('action',choices=['quote','verify','get'])
p.add_argument('origin',nargs='?');p.add_argument('url',nargs='?')
p.add_argument('--authorized',action='store_true',help='You own the target site or have permission to audit it')
p.add_argument('--tx');p.add_argument('--state',default='launchcheck-order.private.json')
a=p.parse_args();path=Path(a.state)
if a.action=='quote':
    if not a.origin or not a.url or not a.authorized:p.error('quote requires origin, URL and --authorized')
    if not a.origin.startswith('https://') and not a.origin.startswith('http://localhost:'):p.error('Use an HTTPS service origin')
    if path.exists():
        state=json.loads(path.read_text())
        if state['origin']!=a.origin.rstrip('/') or state['url']!=a.url:p.error('State file belongs to another request; choose --state with a new filename')
    else:
        state={'origin':a.origin.rstrip('/'),'url':a.url,'key':secrets.token_hex(32)};save(path,state)
    status,data=call(state['origin'],'/api/agent/audit',state['key'],{'url':state['url'],'authorized':True},True)
    if 'id' in data:state['id']=data['id'];save(path,state)
    print(json.dumps({'httpStatus':status,**data},indent=2))
    if status==402:print('Authorize and pay exactly once with your own wallet. Then run verify with the transaction hash. No payment is sent by this script.',file=sys.stderr)
else:
    state=json.loads(path.read_text())
    if 'id' not in state:p.error('First obtain an order with quote')
    if a.action=='verify' and not a.tx:p.error('--tx is required')
    payload={'action':'verify','tx':a.tx} if a.action=='verify' else None
    status,data=call(state['origin'],'/api/orders/'+state['id'],state['key'],payload)
    print(json.dumps({'httpStatus':status,**data},indent=2))
    if status==202:print('Wait at least 60 seconds and verify the same transaction again. Do not pay twice.',file=sys.stderr)
