#!/usr/bin/env python3
import argparse, base64, hashlib, json, os, sys
from datetime import datetime, timezone
PROTOCOL='twix-dap/0.1'; DOMAIN=b'TWIX-DAP-RECORD-v0.1\x00'
def b64u(b): return base64.urlsafe_b64encode(b).rstrip(b'=').decode()
def cj(v):
    if v is None:return 'null'
    if v is True:return 'true'
    if v is False:return 'false'
    if isinstance(v,int):return str(v)
    if isinstance(v,float):raise ValueError('Use decimal strings, not floats')
    if isinstance(v,str):return json.dumps(v,separators=(',',':'),ensure_ascii=False)
    if isinstance(v,list):return '['+','.join(cj(x) for x in v)+']'
    if isinstance(v,dict):return '{'+','.join(json.dumps(k,ensure_ascii=False)+':'+cj(v[k]) for k in sorted(v))+ '}'
    raise ValueError('Unsupported JSON type')
def self_test(args):
    r={'protocol':PROTOCOL,'device_id':'meter:test:001','sequence':1,'schema':'energy.meter.reading/1','measured_at':'2026-08-26T20:00:00Z','previous_hash':None,'payload':{'value':'14832.771','unit':'kWh'}}
    got=hashlib.sha256(DOMAIN+cj(r).encode()).hexdigest(); want='3770ad8a5ff06fb05403926524dae336e3d1bf122ecdb145502822ab8ce03798'
    print('SELF-TEST PASS' if got==want else 'SELF-TEST FAIL'); print(got); raise SystemExit(0 if got==want else 1)
def init(args):
    from nacl.signing import SigningKey
    if os.path.exists(args.key): raise SystemExit('Key already exists')
    sk=SigningKey.generate(); open(args.key,'wb').write(sk.encode()); os.chmod(args.key,0o600)
    print('Device ID:',args.device_id); print('Public key:',b64u(sk.verify_key.encode())); print('Private key written to:',args.key)
def send(args):
    import requests
    from nacl.signing import SigningKey
    if not __import__('re').fullmatch(r'-?[0-9]+(?:\.[0-9]+)?',args.value): raise SystemExit('value must be a decimal string')
    sk=SigningKey(open(args.key,'rb').read()); st={'sequence':0,'last_hash':None}
    if os.path.exists(args.state): st.update(json.load(open(args.state)))
    seq=int(st['sequence'])+1
    record={'protocol':PROTOCOL,'device_id':args.device_id,'sequence':seq,'schema':args.schema,'measured_at':args.measured_at or datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ'),'previous_hash':None if seq==1 else st['last_hash'],'payload':{'value':args.value,'unit':args.unit}}
    digest=hashlib.sha256(DOMAIN+cj(record).encode()).digest(); env={'record':record,'record_hash':digest.hex(),'signature':b64u(sk.sign(digest).signature)}
    r=requests.post(args.endpoint,json=env,timeout=15); data=r.json();
    if not r.ok or not data.get('ok'): raise SystemExit(data.get('error',f'HTTP {r.status_code}'))
    with open(args.state,'w') as f: json.dump({'sequence':seq,'last_hash':digest.hex()},f,indent=2); os.chmod(args.state,0o600)
    print(json.dumps(data,indent=2))
p=argparse.ArgumentParser(description='TWIX TDAP Python meter agent v0.2.0');sub=p.add_subparsers(dest='cmd',required=True)
a=sub.add_parser('self-test');a.set_defaults(fn=self_test)
a=sub.add_parser('init');a.add_argument('--device-id',required=True);a.add_argument('--key',default='./meter.key');a.set_defaults(fn=init)
a=sub.add_parser('send');a.add_argument('--device-id',required=True);a.add_argument('--key',default='./meter.key');a.add_argument('--state',default='./meter-state.json');a.add_argument('--endpoint',default='https://validator.twixchain.com/api/ingest.php');a.add_argument('--value',required=True);a.add_argument('--unit',default='kWh');a.add_argument('--schema',default='energy.meter.reading/1');a.add_argument('--measured-at',dest='measured_at');a.set_defaults(fn=send)
args=p.parse_args();args.fn(args)
