#!/usr/bin/env python3
'''P0.5 fixed-lever node-graph covariance and motion-window audit.'''
from __future__ import annotations
import argparse, json, sys
from dataclasses import asdict
from pathlib import Path
import numpy as np
from scipy.spatial.transform import Rotation
ROOT = Path(__file__).resolve().parents[1]
if str(ROOT) not in sys.path: sys.path.insert(0,str(ROOT))
from rtk_imu.rtk_imu_engineering import _height_reference
from rtk_imu.rtk_imu_multisource import load_unified_sessions
from rtk_imu.rtk_imu_node_graph import (
    build_problem,solve_fixed_lever,solve_fixed_lever_many)
from tools.audit_rtk_imu_factor_consistency import MECHANICAL_L_I_M,_jsonable
from tools.run_rtk_imu_node_graph_fixed_lever import _select_window

def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--manifest',type=Path,required=True)
    parser.add_argument('--output',type=Path,required=True)
    parser.add_argument('--circle-session',required=True)
    parser.add_argument('--left-right-session',required=True)
    parser.add_argument('--slope-session',required=True)
    parser.add_argument('--sample-period-s',type=float,default=1.)
    parser.add_argument('--target-duration-s',type=float,default=15.)
    parser.add_argument('--max-nfev',type=int,default=50)
    parser.add_argument('--rotation-rpy-deg',nargs=3,type=float,
                        default=[.4543066225,-.0026392019,.0122384129])
    parser.add_argument('--mechanical-l-I-m',nargs=3,type=float,
                        default=MECHANICAL_L_I_M.tolist())
    parser.add_argument('--hpr-direct-sigma-rad',type=float,default=.006)
    args = parser.parse_args()
    categories = {'circle':args.circle_session,'left_right':args.left_right_session,
                  'slope':args.slope_session}
    sessions = load_unified_sessions(args.manifest,selected_session_ids=set(categories.values()))
    reference = _height_reference(sessions)
    rotation = Rotation.from_euler('xyz',args.rotation_rpy_deg,degrees=True).as_matrix()
    lever = np.asarray(args.mechanical_l_I_m)
    problems, selections, separate = [], {}, {}
    for category,session_id in categories.items():
        local = [session for session in sessions if session.session_id == session_id]
        segment,selection = _select_window(
            local,reference,args.sample_period_s,args.target_duration_s)
        problem = build_problem(segment,rotation,lever,args.hpr_direct_sigma_rad)
        problems.append(problem); selections[category] = selection
        separate[category] = asdict(solve_fixed_lever(problem,args.max_nfev))
    joint = asdict(solve_fixed_lever_many(problems,args.max_nfev))
    fixed_pass = bool(
        all(value['success'] for value in separate.values()) and joint['success']
        and joint['chi_square_per_dof'] >= .25
        and joint['chi_square_per_dof'] <= 4.
        and joint['final_position_residual_m']['vector_p95'] <= .20
        and joint['final_velocity_residual_m_s']['vector_p95'] <= .50)
    payload = {'scope':'P0.5 three-motion fixed-lever only',
        'translation_variable_enabled':False,
        'manual_prior_loo_bootstrap_sensitivity_called':False,
        'covariance_model':{
            'source':'independent_innovation_audit_three_motion',
            'best_position_xyz_m':[.06,.06,.12],
            'doppler_xyz_m_s':[.15,.15,.30],
            'hpr_direct_angular_rad':args.hpr_direct_sigma_rad,
            'hpr_bridge_rule':'sqrt(direct_sigma^2 + dropout_extra_variance)',
            'imu_preintegration':'unchanged physical covariance',
            'bias_random_walk':'unchanged static/Allan/device model',
            'postfit_global_scale_applied':False},
        'fixed_l_I_m':lever,'selections':selections,
        'separate_fixed_lever':separate,'joint_fixed_lever':joint,
        'fixed_lever_covariance_gate':{
            'chi_square_per_dof_range':[.25,4.],
            'position_vector_p95_max_m':.20,
            'velocity_vector_p95_max_m_s':.50,
            'passed':fixed_pass},
        'whitening_diagnosis':{
            'normalized_scale_consistent':joint['chi_square_per_dof'] >= .25,
            'joint_preintegration_nis_per_dof':
                joint['final_residual_by_factor']['imu_preintegration']['chi_square_per_dof'],
            'joint_preintegration_normalized_p95':
                joint['final_residual_by_factor']['imu_preintegration']['p95_abs'],
            'preintegration_sigma_distribution':joint['preintegration_covariance_sigma'],
            'interpretation':(
                'absolute preintegration sigma is small, but per-node states satisfy process '
                'factors almost exactly while BEST/Doppler/HPR normalized residuals are also '
                'well below one; current factor covariance set is collectively overconservative'
            )},
        'free_lever_unlocked':fixed_pass}
    args.output.parent.mkdir(parents=True,exist_ok=True)
    args.output.write_text(json.dumps(_jsonable(payload),ensure_ascii=False,indent=2,
                                      allow_nan=False)+'\n',encoding='utf-8')
    compact = {'selections':selections,
        'separate':{key:{'success':value['success'],
            'chi_square_per_dof':value['chi_square_per_dof'],
            'position_p95_m':value['final_position_residual_m']['vector_p95'],
            'velocity_p95_m_s':value['final_velocity_residual_m_s']['vector_p95'],
            'factor_stats':value['final_residual_by_factor']}
            for key,value in separate.items()},
        'joint':{'success':joint['success'],'chi_square_per_dof':joint['chi_square_per_dof'],
            'position_p95_m':joint['final_position_residual_m']['vector_p95'],
            'velocity_p95_m_s':joint['final_velocity_residual_m_s']['vector_p95'],
            'factor_stats':joint['final_residual_by_factor']},
        'free_lever_unlocked':fixed_pass}
    print(json.dumps(_jsonable(compact),ensure_ascii=False,indent=2))
    return 0

if __name__ == '__main__':
    raise SystemExit(main())
