#!/usr/bin/env python3
"""Python 3.9+, stdlib only. All funds and destinations are synthetic."""
import json,os,re,sys,time,uuid
from urllib.request import Request,urlopen
from urllib.error import HTTPError,URLError
API='https://api.luniumpay.com'
flow=sys.argv[1] if len(sys.argv)>1 else 'all'
if flow not in ('all','custody','cashin','cashout','payout'):raise SystemExit('Use all, custody, cashin, cashout or payout')
key=os.environ.get('LUNIUM_API_KEY','');run=str(uuid.uuid4());ref='starter-'+run
address='0x1111111111111111111111111111111111111111'
def call(path,body=None,authenticated=True):
    for attempt in range(3):
        headers={'Content-Type':'application/json'}
        if authenticated:headers['X-API-Key']=key
        request=Request(API+path,data=None if body is None else json.dumps(body).encode(),headers=headers,method='GET' if body is None else 'POST')
        try:
            with urlopen(request,timeout=20) as response:return json.load(response)
        except HTTPError as exc:
            if exc.code>=500 and attempt<2:time.sleep(attempt+1);continue
            data=json.load(exc);raise RuntimeError(str(exc.code)+' '+data.get('erro','request_failed')+': '+data.get('detail','')) from None
        except (URLError,TimeoutError):
            if attempt<2:time.sleep(attempt+1);continue
            raise RuntimeError('Network timeout. Reconcile the original external_id before creating another operation.') from None

def test_only(data):
    if data.get('sandbox') is not True:raise RuntimeError('Stopped: response did not confirm sandbox.')
    return data

def wait(path,done):
    for _ in range(40):
        op=test_only(call(path))
        if done(op):return op
        if op.get('status') in ('failed','expired','refunded') or op.get('state') in ('FAILED','EXPIRED','REFUNDED') or op.get('settlement_status')=='failed':raise RuntimeError('Simulation failed: '+path)
        time.sleep(2)
    raise RuntimeError('Still pending. Reconcile '+path+'; keep the same external_id.')

def settled(op):return op.get('status')=='paid' and op.get('settlement_status')=='sent'
def charge(dest,asset='usdc',chain='polygon',recipient=address):
    ext=run+'-'+dest+'-'+chain
    body=dict(amount_cents=100000,payer_tax_number='12345678901',destino=dest,external_id=ext,customer_ref=ref)
    if dest=='cripto':body.update(asset=asset,chain=chain,payout_address=recipient);test_only(call('/cashin/preview',body))
    c=test_only(call('/cashin/charge',body))
    if not c.get('qr_copypaste','').startswith('SANDBOX:NAO_PAGAVEL:'):raise RuntimeError('Stopped: unexpected QR.')
    print('Created',c['cashin_id'],'external_id',ext)
    test_only(call('/sandbox/cashin/'+c['cashin_id']+'/pay',{}));wait('/cashin/'+c['cashin_id']+'/status',settled)
    print('PASS PIX ->', 'custody BRL' if dest=='saldo' else asset+'/'+chain)

if not key:key=call('/keys/sandbox',{'name':'starter-all-flows'},False)['api_key']
if not re.fullmatch(r'lun_test_[A-Za-z0-9_-]{20,200}',key):raise SystemExit('Stopped: only lun_test_ credentials are accepted.')
if call('/keys/me').get('key',{}).get('sandbox') is not True:raise SystemExit('Stopped: account is not confirmed as sandbox.')
print('Sandbox verified. No PIX, exchange order or blockchain transfer will be executed.')
if flow in ('all','custody','payout'):
    charge('saldo');balance=test_only(call('/saldo?customer_ref='+ref))
    if balance['disponivel_cents']<=0:raise RuntimeError('No available custody balance.')
    if flow!='payout':
        w=test_only(call('/saldo/sacar-cripto',dict(amount_cents=20000,tax_number='12345678901',customer_ref=ref,asset='usdc',chain='base',payout_address=address,external_id=run+'-withdraw')))
        wait('/cashin/'+w['cashin_id']+'/status',settled);print('PASS custody -> USDC/Base')
    if flow!='custody':
        p=test_only(call('/payouts',dict(amount_cents=20000,pix_key='sandbox@example.invalid',pix_key_type='email',customer_ref=ref,external_id=run+'-payout')))
        wait('/payouts/'+p['payout_id'],lambda op:op.get('status')=='sent');print('PASS custody -> PIX')
    if flow=='all':test_only(call('/saldo/transferir',dict(amount_cents=1000,from_customer_ref=ref,to_customer_ref='casa',external_id=run+'-transfer')));print('PASS subaccount -> house transfer')
    ledger=test_only(call('/saldo/extrato?customer_ref='+ref));print('Ledger entries:',len(ledger['movimentos']))
if flow in ('all','cashin'):
    charge('cripto','btc','btc','1BoatSLRHtKNngkdXEeobR76b53LETtpyT');charge('cripto','usdc','polygon')
if flow in ('all','cashout'):
    q=test_only(call('/cash-outs',dict(asset='USDT',network='polygon',amount='10',pix_key='sandbox@example.invalid',pix_key_type='email',external_id=run+'-cashout')))
    test_only(call('/cash-outs/'+q['cashout_id']+'/accept',{}));wait('/cash-outs/'+q['cashout_id'],lambda op:op.get('state')=='COMPLETED');print('PASS USDT -> PIX')
print('Recorded simulation events:',len(test_only(call('/sandbox/events'))['events']))
print('Completed. Error scenarios and webhooks: https://docs.luniumpay.com/sandbox')
