"""Offline schema audit; optionally re-extract from pinned gzip files.
python tests/test_resource_schema.py --source-dir /path/to/downloaded/files
No downloads, game calls, or writes into a game installation.
"""
from pathlib import Path
import argparse,json,struct,hashlib,gzip,sys,re
R=Path(__file__).resolve().parents[1]
sys.path.insert(0,str(R/'tools'))
from parse_unit_components import parse_types,find_section,dlhash
from archive_writer import murmur64
p=argparse.ArgumentParser();p.add_argument('--source-dir',type=Path);args=p.parse_args()
s=json.loads((R/'validation/resource-schema.json').read_text());maps=s['mappings'];assert len(maps)==12
source=(R/'src/unit_ref_bridge.lua').read_text();table=source.split('local model_resource={',1)[1].split('}',1)[0];actual=dict(re.findall(r"\['([0-9a-f]{16})'\]='([0-9a-f]{16})'",table))
expected={k:v[0]['engine_unit_resource']for k,v in maps.items()};assert actual==expected
assert sum(k!=v for k,v in actual.items())==3
assert f"{murmur64(b'content/fac_helldivers/vehicles/combat_walker/combat_walker'):016x}"=='79e4b3d2da5e45e3'
for h,rows in maps.items():
 assert len(rows)==1
 row=rows[0];assert row['component']=='UnitComponent'
 assert f"{struct.unpack_from('<Q',bytes.fromhex(row['record_hex']))[0]:016x}"==row['engine_unit_resource']
 sec=s['sections']['UnitComponentData'];typ=s['types']['UnitComponentData'];ix,records=typ['members']
 assert row['record_offset']==sec['data_offset']+records['offset']+row['index']*s['types']['UnitComponent']['size']
 assert 0<=row['index']<records['array_len']
 assert sec['data_offset']<=row['index_record_offset']<sec['data_offset']+ix['size']
 assert (row['index_record_offset']-sec['data_offset'])%s['types']['ComponentIndexData']['size']==0
verified=False
if args.source_dir:
 blobs={}
 for key in ['dl_library.dl_typelib','generated_entities.dl_bin']:
  b=(args.source_dir/(key+'.gz')).read_bytes();ref=s['provenance']['files']['datalibrary/'+key+'.gz'];assert len(b)==ref['bytes']and hashlib.sha256(b).hexdigest()==ref['sha256']
  blobs[key]=gzip.decompress(b)
 tl=blobs['dl_library.dl_typelib'];b=blobs['generated_entities.dl_bin'];assert hashlib.sha256(tl).hexdigest()==s['typelib_sha256'];assert hashlib.sha256(b).hexdigest()==s['entities_sha256']
 types=parse_types(tl)
 for k,v in s['types'].items():assert types[dlhash(k)]==v,k
 off,base,size=find_section(b,dlhash('UnitComponentData'));section=s['sections']['UnitComponentData'];assert (off,base,size)==(section['offset'],section['data_offset'],section['size'])
 typ=types[dlhash('UnitComponentData')];ix,records=typ['members'];it=types[ix['type_id']];rt=types[records['type_id']]
 assert ix['atom']==records['atom']==2 and ix['storage']==records['storage']==20
 assert ix['type_id']==dlhash('ComponentIndexData')and records['type_id']==dlhash('UnitComponent')
 assert ix['array_len']*it['size']==ix['size'] and records['array_len']*rt['size']==records['size']
 assert typ['size']==size and records['offset']+records['size']==size
 assert it['members'][0]['offset']==0 and it['members'][1]['offset']==8 and rt['members'][0]['offset']==0
 for h,rows in maps.items():
  # Independently locate each resource key in the bounded hash table.
  row=rows[0];needle=bytes.fromhex(h)[::-1];found=[];at=base
  stop=base+ix['size']
  while True:
   at=b.find(needle,at,stop)
   if at<0:break
   if (at-base)%it['size']==0:found.append(at)
   at+=1
  assert found==[row['index_record_offset']]
  index=struct.unpack_from('<I',b,found[0]+8)[0];assert index==row['index']
  pos=base+records['offset']+index*rt['size'];assert b[pos:pos+rt['size']].hex()==row['record_hex']
 verified=True
print(json.dumps({'schema_entries':12,'shared_model_body_types':4,'distinct_arm_models':8,'unknown_resources_fail_closed':True,'production_table_matches_records':True,'pinned_external_data_reextracted':verified,'installed_game_data_verified':False,'live_display_verified':False}))
