"""Read-only native Fusion audit for the generated RS06 r02 document."""
from pathlib import Path
import hashlib
import json
import adsk.core as ac
import adsk.fusion as af

HERE = Path(__file__).resolve().parent


def run(_context):
    d = af.Design.cast(ac.Application.get().activeProduct)
    assert d.parentDocument.name == 'Shappy V2 - RS06 Whole Robot r02'
    p = json.loads((HERE / 'parameters.json').read_text())
    analysis = json.loads((HERE.parent / 'rs06_concept/analysis.json').read_text())
    checks = json.loads((HERE / 'cad/cad_checks.json').read_text())
    for filename, key in [('fusion_build.py', 'source_sha256'), ('parameters.json', 'parameters_sha256')]:
        assert hashlib.sha256((HERE / filename).read_bytes()).hexdigest() == checks[key]
    old_parameters=json.loads((HERE.parent / 'rs06_concept/parameters.json').read_text())
    assert p['kinematics']==old_parameters['kinematics'] and p['pose']==old_parameters['pose']
    healthy = all(i.healthState == af.FeatureHealthStates.HealthyFeatureHealthState for i in d.timeline)
    assert healthy
    expected = analysis['poses']['stand']['foot_centers_mm']
    feet, housings, bodies, uppers = {}, 0, 0, 0
    for o in d.rootComponent.occurrences:
        if o.component.name == 'PETG_upper_link_200':
            assert o.component.bRepBodies.count == 1
            uppers += 1
        for b in o.component.bRepBodies:
            assert b.isSolid
            bodies += 1
            housings += b.name == 'RS06_D90_L55_reference_housing'
            if o.component.name == 'D40_foot_ball_REFERENCE':
                c = b.physicalProperties.centerOfMass
                c.transformBy(o.transform2)
                xyz = [getattr(c, a) * 10 for a in 'xyz']
                leg = ('F' if xyz[0] > 0 else 'R') + ('L' if xyz[1] > 0 else 'R')
                feet[leg] = xyz
    assert housings == p['motor']['count'] == 12
    assert uppers == 4 and bodies == checks['body_count']
    profiles=[o for o in d.rootComponent.occurrences if o.component.name.startswith(('Long_profile_', 'Lower_cross_profile_'))]
    assert len(profiles)==6 and all(o.component.bRepBodies.count==1 for o in profiles)
    assert not any(o.component.name=='Pocketed_A6061_body_frames' for o in d.rootComponent.occurrences)
    for o in profiles:
        name=o.component.name
        if name.startswith('Long_profile_'):
            side,level=map(int,name.removeprefix('Long_profile_').split('_'))
            expected_center=[0,side*(145 if level>0 else 125),checks['haa_height_mm']+level*55]
        else:expected_center=[float(name.removeprefix('Lower_cross_profile_')),0,checks['haa_height_mm']-55]
        actual=o.transform2.translation
        assert max(abs(getattr(actual,a)*10-expected_center[i]) for i,a in enumerate('xyz'))<1e-6
    tray=next(o.component for o in d.rootComponent.occurrences if o.component.name=='P2S_PETG_battery_tray')
    assert tray.bRepBodies.count==1
    assert set(feet) == set(expected)
    error = max(abs(feet[l][i] - expected[l][i]) for l in feet for i in range(3))
    assert error < 1e-6
    # Eight intentional overlaps: four belts and two tooth-envelope pulleys each.
    collisions = checks['standing_interferences']
    assert len(collisions) == 8
    assert all('600S5M250_belt_PITCH_PATH_not_teeth' in c['two'] and
               c['one'].startswith('S5M_belt_drive_40T_600_25 / ') for c in collisions)
    masses = {}
    for kind, density in [('aluminum', 2.70), ('PETG', 1.27), ('steel', 7.85)]:
        volume = sum(b['volume_mm3'] for b in checks['body_records'] if b['kind'] == kind)
        masses[kind] = {'volume_mm3': volume, 'assumed_density_g_cm3': density,
                        'mass_kg': volume * density / 1e6}
    motor_mass = housings * p['motor']['catalog_mass_g'] / 1000
    profile_mass=(4*340+2*230)/1000*p['frame']['profile_linear_mass_kg_m']
    subtotal = motor_mass + profile_mass + sum(m['mass_kg'] for m in masses.values())
    frame_components={'Chassis_motor_end_plates_5mm','Lower_crossmember_flat_gussets_3mm','Upper_X_braces_2mm','Upper_brace_spacers_2mm'}
    frame_aluminum=sum(b['volume_mm3'] for b in checks['body_records'] if b['component'] in frame_components)*2.7e-6
    frame_hardware=sum(b['volume_mm3'] for b in checks['body_records'] if b['component']=='Chassis_fasteners_and_Tnuts')*7.85e-6
    tray_mass=tray.bRepBodies.item(0).volume*1.27/1000
    printed=[]
    for o in d.rootComponent.occurrences:
        c=o.component
        if c.name not in ['PETG_upper_link_200','C_diagonal_lower_link_195','Replaceable_PETG_foot_cup','P2S_PETG_battery_tray']:continue
        b=c.bRepBodies.item(0);bb=b.boundingBox
        size=[(getattr(bb.maxPoint,a)-getattr(bb.minPoint,a))*10 for a in 'xyz']
        assert all(v<256 for v in size)
        printed.append({'component':c.name,'size_mm':size,'volume_mm3':b.volume*1000})
    assert len(printed)==13
    chassis_records=[b for b in checks['body_records'] if b['component']=='Chassis_fasteners_and_Tnuts']
    assert sum('HAA_stator_M3x8' in b['body'] for b in chassis_records)==32
    assert sum('Profile_end_M5x16' in b['body'] for b in chassis_records)==8
    assert sum('HNTT5_5_seating_proxy' in b['body'] for b in chassis_records)==20
    assert sum('_thread_free_reference' in b['body'] for b in chassis_records)==60
    old_checks=json.loads((HERE.parent/'rs06_concept/cad/cad_checks.json').read_text())
    original={(b['component'],b['body']):b for b in old_checks['body_records'] if b['component']!='Pocketed_A6061_body_frames'}
    current={(b['component'],b['body']):b for b in checks['body_records']}
    for key,before in original.items():
        after=current[key]
        assert abs(before['volume_mm3']-after['volume_mm3'])<1e-4
        assert max(abs(before['bounds_mm'][j][k]-after['bounds_mm'][j][k]) for j in range(2) for k in range(3))<1e-5
    assert len(original)==167
    geometry=[]
    for o in d.rootComponent.occurrences:
        for b in o.component.bRepBodies:geometry.append((o.component.name,b.name,b.createForAssemblyContext(o)))
    sets={'profiles':[r for r in geometry if r[0].startswith(('Long_profile','Lower_cross_profile'))],
          'motors':[r for r in geometry if r[1]=='RS06_D90_L55_reference_housing'],
          'belts':[r for r in geometry if 'belt_PITCH_PATH' in r[1]],
          'plates':[r for r in geometry if r[0]=='Chassis_motor_end_plates_5mm'],
          'arms':[r for r in geometry if r[0].endswith('_abduction_carrier_arm')],
          'upper':[r for r in geometry if r[0]=='PETG_upper_link_200']}
    clearances={}
    for g,h in [('profiles','motors'),('profiles','belts'),('plates','arms'),('plates','upper')]:
        value=min(ac.Application.get().measureManager.measureMinimumDistance(a[2],b[2]).value*10 for a in sets[g] for b in sets[h])
        assert value>.99
        clearances[g+'_to_'+h]=value
    report = {
        'checked_on': '2026-10-28', 'revision': p['revision'],
        'scope': 'current nominal pose native solids, placement, source identity and partial mass arithmetic',
        'result': 'pass within stated scope', 'native_bodies': bodies,
        'motor_housings': housings, 'connected_upper_links': uppers,
        'timeline_healthy': healthy, 'foot_centers_mm': feet,
        'profiles': {'count':6,'longitudinal_length_mm':340,'cross_length_mm':230,'total_cut_length_m':1.82,'catalog_mass_kg':profile_mass},
        'chassis_fasteners': {'M3x8':32,'button_M5x16':8,'M5x8':14,'M5x10':2,'M5x12':4,'Tnut_proxies':20,'top_brace_washers':4},
        'unchanged_r01_bodies_by_volume_and_world_bounds':len(original),
        'nominal_pose_minimum_clearances_mm':clearances,
        'printed_part_bounds_P2S':printed,
        'frame_mass_comparison':{'r01_body_without_frame_fasteners_kg':1.6986502542571693,
            'r02_profiles_plus_metal_plates_braces_gussets_kg':profile_mass+frame_aluminum,
            'r02_added_frame_fastener_proxies_kg':frame_hardware,
            'r02_body_including_fasteners_kg':profile_mass+frame_aluminum+frame_hardware,
            'r02_new_printed_tray_kg':tray_mass,
            'scope':'catalog profile mass plus CAD reference volumes; actual fastener and profile weights must be confirmed; r01 omitted frame hardware'},
        'max_foot_center_difference_from_kinematics_mm': error,
        'unexpected_positive_volume_interferences': 0,
        'intentional_belt_tooth_envelope_interferences': len(collisions),
        'mass_estimate': {'motor_catalog_mass_kg': motor_mass, 'modeled_materials': masses,
                          'profile_catalog_mass_kg':profile_mass,'partial_subtotal_kg': subtotal,
                          'excluded': ['pulleys and belts', 'bearings', 'foot elastomer',
                                       'battery and electronics', 'wiring', 'unmodeled fasteners'],
                          'basis': 'solid CAD volumes at assumed densities; not a measured mass or complete BOM'},
        'not_verified': ['joint motion clearance', 'strength and stiffness', 'belt preload and capacity',
                         'fastener fits and torque transmission', 'thermal duty', 'physical operation'],
        'source_sha256': {n: hashlib.sha256((HERE / n).read_bytes()).hexdigest()
                          for n in ['verify_cad.py', 'fusion_build.py', 'parameters.json',
                                    'cad/cad_checks.json', 'cad/shappy_rs06_r02.step',
                                    'cad/shappy_rs06_r02.f3d', 'viewer/scene.json', '../rs06_concept/analysis.json',
                                    '../rs06_concept/parameters.json','../rs06_concept/cad/cad_checks.json']},
    }
    (HERE / 'cad/verification.json').write_text(json.dumps(report, indent=2) + '\n')
    print(json.dumps({k: report[k] for k in ['result', 'native_bodies', 'motor_housings',
                                            'max_foot_center_difference_from_kinematics_mm']}))
    print('Partial solid-model mass subtotal kg:', subtotal)
