import json, urllib.request, sys, hmac, hashlib, struct, time, base64
B='http://127.0.0.1:8090/api'
TOK=None
def call(m, path, body=None, tok=None, raw=False):
    req=urllib.request.Request(B+path, method=m, data=json.dumps(body).encode() if body is not None else None, headers={'Content-Type':'application/json', **({'Authorization':'Bearer '+(tok or TOK)} if (tok or TOK) else {})})
    try: r=urllib.request.urlopen(req); d=r.read()
    except urllib.error.HTTPError as e: d=e.read(); print('  !!', m, path, e.code, d[:300].decode()); return None
    return d.decode() if raw else json.loads(d)
def totp(secret):
    key=base64.b32decode(secret+'='*((8-len(secret)%8)%8)); c=int(time.time())//30
    h=hmac.new(key, struct.pack('>Q',c), hashlib.sha1).digest(); o=h[-1]&15
    return '%06d' % ((struct.unpack('>I',h[o:o+4])[0]&0x7fffffff)%1000000)
print(call('GET','/setup/status'))
s=call('POST','/setup',{'firm_name':'Test & Co, Company Secretaries','name':'Test Partner','email':'p@test.test','password':'correct-horse-battery','membership_no':'FCS 1234','cop_no':'999'})
print('setup', s and s.keys())
sec=s['totp_secret']
l=call('POST','/auth/login',{'email':'p@test.test','password':'correct-horse-battery','code':totp(sec)})
print('login', {k:v for k,v in l.items() if k!='token'})
TOK=l.get('token')
if not TOK:
    l=call('POST','/auth/login',{'email':'p@test.test','password':'correct-horse-battery','code':totp(sec)}); TOK=l['token']
me=call('GET','/auth/me'); print('me', me['user']['name'], me['firm']['name'], me['rules_version'])
con=call('GET','/console'); print('console rows', len(con['companies']), [ (r['name'], r.get('status')) for r in con['companies']][:3], 'season', con.get('season'))
cid=con['companies'][0]['id']
co=call('GET',f'/companies/{cid}'); print('company', co['company']['name'], 'members', len(co['members']), 'dirs', len(co['directors']), 'next', co.get('next_due'), 'tasks', co.get('sandbox_tasks'))
cal=call('GET',f'/companies/{cid}/calendar'); print('calendar', [(r['rule_id'], r['status'], r['due_on']) for r in cal['items'] if r['status'] not in ('filed','done','later')])
tr=call('GET',f'/companies/{cid}/trail'); print('trail', tr['verify'], len(tr['entries']))
ev=call('GET',f'/companies/{cid}/events'); print('events', len(ev.get('events',ev.get('rows',[]))))
# allotment preview
FORM={'allotment_date':'2026-09-20','kind':'private_placement','issue_price':1200,'board_ref':'BM/2026-27/05','allottees':[{'name':'Peak Ridge Ventures LLP','shares':1000},{'name':'New Investor LLP','shares':500,'address':'Mumbai'}],'approved_total':1500,'consideration_received_on':'2026-09-18'}
pv=call('POST',f'/companies/{cid}/events/preview',{'type':'allotment','form':FORM})
print('preview', pv and (pv.get('errors'), [w for w in pv.get('writes',[])][:3], pv.get('calendar') and [(c['rule_id'],c['due_on']) for c in pv['calendar']][:3]))
if pv and not pv.get('errors'):
    r=call('POST',f'/companies/{cid}/events',{'type':'allotment','form':FORM,'head_hash':pv['head_hash']})
    print('recorded', r and {k:r[k] for k in r if k in ('ok','entries','entry')})
cal=call('GET',f'/companies/{cid}/calendar'); pas=[r for r in cal['items'] if r['rule_id']=='PAS-3' and r['status'] in ('due','later','overdue')]
print('PAS-3 rows', [(r['due_on'],r['status'],r['id'][:6]) for r in pas])
if pas:
    print('explain', call('GET',f"/companies/{cid}/calendar/{pas[0]['id']}/explain")['text'][:200])
    sr=call('POST',f"/companies/{cid}/calendar/{pas[0]['id']}/srn",{'srn':'T12345678','filed_on':'2026-09-25'}); print('srn', sr and sr.get('ok'), [ (r['rule_id'],r['status']) for r in sr['items'] if r['status'] in ('overdue','ask')])
# meetings
ms=call('GET',f'/companies/{cid}/meetings'); print('meetings', len(ms['meetings']), ms['next_ref'], 'circulars', len(ms['circulars']))
m=call('POST',f'/companies/{cid}/meetings',{'meeting_date':'2026-10-09','meeting_time':'16:00','place':'Registered office'}); mid=m['id']; print('meeting', call('GET',f'/companies/{cid}/meetings/{mid}')['meeting']['ref'])
print('notice', call('POST',f'/companies/{cid}/meetings/{mid}/notice',{'sent_on':'2026-09-28'}))
print('agenda', call('POST',f'/companies/{cid}/meetings/{mid}/agenda',{'agenda':[{'text':'Leave of absence','resolution':False},{'text':'Approve the audited accounts for FY 2025-26','resolution':True},{'text':'Renew the lease of the registered office','resolution':True}]}))
full=call('GET',f'/companies/{cid}/meetings/{mid}'); dirs=full['meeting']['directors']; print('directors', [d['name'] for d in dirs])
print('minutes-refusal', call('POST',f'/companies/{cid}/meetings/{mid}/minutes',{'draft_with_assist':True}))
for i,d in enumerate(dirs): print('attendance', call('POST',f'/companies/{cid}/meetings/{mid}/attendance',{'person_id':d['person_id'],'present':True,'chair':i==0,'interest_items':[3] if i==1 else []}))
dm=call('POST',f'/companies/{cid}/meetings/{mid}/minutes',{'draft_with_assist':True}); print('minutes-draft', dm and dm.get('text','')[:300].replace('\n',' | '))
print('minutes-hand', call('POST',f'/companies/{cid}/meetings/{mid}/minutes',{'text':dm['text']+'\nChecked by hand.'}))
print('resolutions', call('POST',f'/companies/{cid}/meetings/{mid}/resolutions',{'resolutions':[{'n':2,'text':'RESOLVED THAT the audited financial statements be approved'}]}))
print('circulate', call('POST',f'/companies/{cid}/meetings/{mid}/circulate',{}))
# docs
docs=call('GET',f'/companies/{cid}/documents'); print('docs', len(docs['documents']), 'classes', len(docs['classes']), 'auth', [(a['name'],bool(a['authority'])) for a in docs['authorities']])
dr=call('POST',f'/companies/{cid}/documents/draft',{'class':'cover','form':'PAS-3'}); print('draft', dr['draft']['title'])
d=call('POST',f'/companies/{cid}/documents',{'class':'cover','title':dr['draft']['title'],'body':dr['draft']['body'],'signer_person_id':dirs[0]['person_id']}); did=d['id']; print('doc created', d['document']['status'])
print('options', [(o['key'],o['enabled']) for o in call('GET',f'/companies/{cid}/documents/{did}')['options']])
print('request', call('POST',f'/companies/{cid}/documents/{did}/request',{}))
# signatures page, seats, packs, letterheads, firm, rules, ask
print('signatures', {k:len(v) for k,v in call('GET','/signatures').items()})
seats=call('GET',f'/companies/{cid}/seats'); print('seats', [(s['role'],s['status'],s['pill']) for s in seats['seats']])
print('packs', [(s['n'],s['status']) for s in call('GET',f'/companies/{cid}/packs')['sections']])
print('letterhead', call('GET',f'/companies/{cid}/letterhead')['company']['design'])
print('firm', {k:(len(v) if isinstance(v,list) else '.') for k,v in call('GET','/firm').items()})
print('rules', len(call('GET','/rules')['rules']))
print('ask', call('POST','/assist/ask',{'q':'who held shares in Keel Sandbox on 31 March 2026?'})['text'][:200])
print('ask2', call('POST','/assist/ask',{'q':'what is the late fee if sandbox files DIR-12 late'})['kind'])
print('recon', call('GET',f'/companies/{cid}/recon/explain')['text'][:150])
print('narrative', call('GET',f'/companies/{cid}/report/narrative?month=2026-09')['draft'][:200])
for w in ['MGT-1','DIRECTORS_KMP','CHG-7','pack','report','extract']:
    h=call('GET',f'/companies/{cid}/print/{w}',raw=True); print('print',w, h and len(h), h and ('<table' in h))
h=call('GET',f'/companies/{cid}/print/document?document={did}',raw=True); print('print doc', h and len(h))
# seat flow
tok=[s for s in seats['seats'] if s['role']=='founder'][0]
import sqlite3; db=sqlite3.connect('/root/keel-app/api/storage/keel.db'); ftok=db.execute("select token from seats where role='founder' and company_id=?",(cid,)).fetchone()[0]
so=call('POST',f'/seat/open/{ftok}'); print('seat open', so and so.keys()); ST=so['token']
print('seat me', call('GET','/seat/me',tok=ST)['consent_needed'])
print('seat book', {k:v for k,v in call('GET','/seat/book',tok=ST).items() if k in ('folios','shares','paid_up')})
ap=call('GET','/seat/approve',tok=ST); print('approve list', [(c['ref'],c['status'],c['assents']) for c in ap['circulars']])
if ap['circulars']: print('approve', call('POST',f"/seat/approve/{ap['circulars'][0]['id']}",{'interest_declared':True},tok=ST))
sg=call('GET','/seat/sign',tok=ST); print('sign list', [(d['title'][:30],d['status']) for d in sg['documents']], 'auth', sg['authority'])
print('grant', call('POST','/seat/authority',{'classes':['ctc','notice','cover','declaration'],'ends_on':'2027-03-31','money_limit_inr':2500000},tok=ST))
print('numbers', call('POST','/seat/numbers',{'month':'2026-08','revenue':3840000,'burn':2160000,'cash':41200000,'headcount':23},tok=ST))
# firm signs the cover under authority (typed)
print('firm sign', call('POST',f'/companies/{cid}/documents/{did}/sign',{'method':'typed','typed_name':'Test Partner','consent':True}))
print('signatures2', {k:len(v) for k,v in call('GET','/signatures').items()})
tr=call('GET',f'/companies/{cid}/trail'); print('trail2', tr['verify']['ok'], tr['verify']['checked'], tr['reference'])
print('verify public', call('POST','/verify',{'reference':tr['reference']}))
print('verify hash', call('POST','/verify',{'reference':tr['entries'][0]['hash']})['result'])
# new company day-one
nc=call('POST','/companies',{'cin':'U62011TN2026PTC187344','name':'Vaigai Software Pvt Ltd','incorporation_date':'2026-09-14','class':'small_private','state':'TN','subscribers':[{'name':'A Raman','shares':6000},{'name':'B Devi','shares':4000}],'directors':[{'name':'A Raman','din':'11111111'},{'name':'B Devi','din':'22222222'}],'founder_email':'raman@example.test','authorised_capital_inr':100000})
print('new co', nc); nid=nc['id']
d1=call('GET',f'/companies/{nid}/dayone'); print('dayone', [(c['form'] or c['title'][:20], c['due_on']) for c in d1['calendar']])
print('attest typed (should refuse)', call('POST',f'/companies/{nid}/dayone/attest',{'method':'typed','typed_name':'x','consent':True}))
# migration company
mc=call('POST','/companies',{'cin':'U01100MH2015PTC260000','name':'Sahyadri Agro Pvt Ltd','incorporation_date':'2015-02-10','class':'private','state':'MH','mode':'migration','nominal_value':10}); mid2=mc['id']; print('mig co', mc)
mg=call('GET',f'/companies/{mid2}/migration'); print('migration', mg['migration']['step'], [(s['kind'],s['status']) for s in mg['sources']][:3], mg['counts'])
print('nightly:'); import subprocess; print(subprocess.run(['php','/root/keel-app/api/bin/nightly.php'],capture_output=True,text=True).stdout[-300:])
