# -*- coding: utf-8 -*-
import os, sys, numpy as np
import pinocchio as pin
from omegaconf import OmegaConf


sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from core.interaction_estimater import InteractionEstimator


# —— 误差函数（更鲁棒的相对误差 + 绝对误差）——
def rel_err(a, b, eps=1e-9):
    """对称式相对误差：‖a-b‖ / max(0.5(‖a‖+‖b‖), eps)"""
    na, nb = np.linalg.norm(a), np.linalg.norm(b)
    denom = max(0.5 * (na + nb), eps)
    return np.linalg.norm(a - b) / denom


def abs_err(a, b):
    """绝对误差：‖a-b‖"""
    return np.linalg.norm(a - b)


# —— 固定范数的扳手采样（方向随机）——
def sample_wrench_fixed_norm(F_lin_norm=10.0, F_ang_norm=2.0, rng=None):
    """
    在线性部分和角部分分别固定范数，方向随机的 6D 扳手采样
    """
    rng = np.random.default_rng() if rng is None else rng
    v = rng.standard_normal(6)
    f = v[:3]
    m = v[3:]
    f = f / (np.linalg.norm(f) + 1e-12) * F_lin_norm
    m = m / (np.linalg.norm(m) + 1e-12) * F_ang_norm
    return np.hstack([f, m])


def load_model(urdf_path: str) -> pin.Model:
    assert os.path.exists(urdf_path), f"URDF not found: {urdf_path}"
    model = pin.buildModelFromUrdf(urdf_path)
    print(f"[URDF] model loaded: nq={model.nq}, nv={model.nv}, njoints={model.njoints}")
    return model


def oracle_test(model,
                chest_frame: str = 'slave_shoulder',
                ee_frame: str = 'slave_ee',
                n_trials: int = 300,
                noise_tau_std: float = 0.0,
                lambda_damp: float = 1e-6,
                seed: int = 11,
                sv_min_skip: float = 1e-6):
    """
    使用 InteractionEstimator 的“理想/带噪声”仿真：
    - 通过动力学模型生成 tau_model
    - 叠加胸腔系扳手 CJ^T F_true 和测量噪声得到 tau_meas
    - 调用 est.estimate 恢复 tau_int, F_est
    - 统计相对/绝对误差的均值、方差、p90
    """
    rng = np.random.default_rng(seed)
    est = InteractionEstimator(model, chest_frame, ee_frame, lambda_damp=lambda_damp)
    data = model.createData()

    errs_rel, errs_abs = [], []
    kept = 0
    for k in range(n_trials):
        # 随机状态
        q = pin.randomConfiguration(model)
        qd = np.random.randn(model.nv) * 0.1
        qdd = np.random.randn(model.nv) * 0.2

        # 动力学力矩 tau_model
        M = pin.crba(model, data, q)
        M = (M + M.T) - np.diag(M.diagonal())
        nle = pin.nonLinearEffects(model, data, q, qd)
        g = pin.computeGeneralizedGravity(model, data, q)
        Cqd = nle - g
        tau_model = M @ qdd + Cqd + g

        # 胸腔系雅可比 CJ
        CJ = est._chest_jacobian(q, qd)  # 6 x nv
        svals = np.linalg.svd(CJ, compute_uv=False)
        if svals[-1] < sv_min_skip:
            # 跳过奇异邻域样本
            continue

        # 固定范数扳手
        CF_true = sample_wrench_fixed_norm(10.0, 2.0, rng=rng)

        # 合成“测得力矩” tau_meas = tau_model + CJ^T F_true + noise
        tau_meas = tau_model + CJ.T @ CF_true + np.random.randn(model.nv) * noise_tau_std

        # 利用 estimator 做估计
        tau_int, CF_est, CJ_check = est.estimate(q, qd, qdd, tau_meas)

        # 误差统计
        errs_rel.append(rel_err(CF_est, CF_true))
        errs_abs.append(abs_err(CF_est, CF_true))
        kept += 1

        # 一致性：回投误差 & 功率一致性（抽样打印）
        if k % 50 == 0:
            back_err = rel_err(CJ_check.T @ CF_est, tau_int)
            v_C = CJ @ qd
            Pq = float(tau_int @ qd)
            Pv = float(CF_true @ v_C)
            print(
                f"[{k:03d}] rel={errs_rel[-1]:.3e}, abs={errs_abs[-1]:.3e}, "
                f"back={back_err:.3e}, power={abs(Pq - Pv):.3e}, sv_min={svals[-1]:.2e}"
            )

    errs_rel = np.array(errs_rel)
    errs_abs = np.array(errs_abs)
    if kept == 0:
        print("[Oracle] all samples skipped by sv_min filter; try smaller sv_min_skip.")
        # 返回空 summary
        return dict(
            errs_rel=errs_rel,
            errs_abs=errs_abs,
            kept=0,
            mean_rel=np.nan,
            std_rel=np.nan,
            p90_rel=np.nan,
            mean_abs=np.nan,
            std_abs=np.nan,
            p90_abs=np.nan,
        )

    mean_rel = float(errs_rel.mean())
    std_rel = float(errs_rel.std())
    p90_rel = float(np.percentile(errs_rel, 90))

    mean_abs = float(errs_abs.mean())
    std_abs = float(errs_abs.std())
    p90_abs = float(np.percentile(errs_abs, 90))

    print(
        f"[Oracle REL] mean={mean_rel:.3e}, std={std_rel:.3e}, "
        f"median={np.median(errs_rel):.3e}, p90={p90_rel:.3e}"
    )
    print(
        f"[Oracle ABS] mean={mean_abs:.3e}, std={std_abs:.3e}, "
        f"median={np.median(errs_abs):.3e}, p90={p90_abs:.3e}"
    )

    summary = dict(
        errs_rel=errs_rel,
        errs_abs=errs_abs,
        kept=kept,
        mean_rel=mean_rel,
        std_rel=std_rel,
        p90_rel=p90_rel,
        mean_abs=mean_abs,
        std_abs=std_abs,
        p90_abs=p90_abs,
    )
    return summary


def oracle_test_baseline(model,
                         chest_frame: str = 'slave_shoulder',
                         ee_frame: str = 'slave_ee',
                         n_trials: int = 300,
                         noise_tau_std: float = 0.05,
                         seed: int = 17,
                         sv_min_skip: float = 1e-6):
    """
    基线：不调用 InteractionEstimator 的残差/阻尼公式，
    直接用未阻尼伪逆解胸腔扳手：

        tau_int_true = tau_meas - tau_model
        F_est = (CJ CJ^T)^{-1} CJ tau_int_true

    用来对比在同样噪声/配置下的数值稳定性。
    """
    rng = np.random.default_rng(seed)
    est_tmp = InteractionEstimator(model, chest_frame, ee_frame, lambda_damp=0.0)
    data = model.createData()

    errs_rel, errs_abs = [], []
    kept = 0
    for k in range(n_trials):
        # 随机状态
        q = pin.randomConfiguration(model)
        qd = np.random.randn(model.nv) * 0.1
        qdd = np.random.randn(model.nv) * 0.2

        # 动力学力矩 tau_model
        M = pin.crba(model, data, q)
        M = (M + M.T) - np.diag(M.diagonal())
        nle = pin.nonLinearEffects(model, data, q, qd)
        g = pin.computeGeneralizedGravity(model, data, q)
        Cqd = nle - g
        tau_model = M @ qdd + Cqd + g

        # 胸腔雅可比 CJ
        CJ = est_tmp._chest_jacobian(q, qd)
        svals = np.linalg.svd(CJ, compute_uv=False)
        if svals[-1] < sv_min_skip:
            continue

        # 固定范数扳手（与 oracle_test 相同范数）
        CF_true = sample_wrench_fixed_norm(10.0, 2.0, rng=rng)

        # 测得力矩（含噪声）
        tau_meas = tau_model + CJ.T @ CF_true + np.random.randn(model.nv) * noise_tau_std

        # “真实”关节残差力矩（在仿真中我们知道 tau_model）
        tau_int_true = tau_meas - tau_model

        # 未阻尼伪逆 (CJ CJ^T)^{-1} CJ tau_int_true
        JJt = CJ @ CJ.T
        try:
            F_est = np.linalg.solve(JJt, CJ @ tau_int_true)
        except np.linalg.LinAlgError:
            # 数值奇异，跳过该样本
            continue

        errs_rel.append(rel_err(F_est, CF_true))
        errs_abs.append(abs_err(F_est, CF_true))
        kept += 1

        if k % 50 == 0:
            back_err = rel_err(CJ.T @ F_est, tau_int_true)
            print(
                f"[BASE {k:03d}] rel={errs_rel[-1]:.3e}, abs={errs_abs[-1]:.3e}, "
                f"back={back_err:.3e}, sv_min={svals[-1]:.2e}"
            )

    errs_rel = np.array(errs_rel)
    errs_abs = np.array(errs_abs)
    if kept == 0:
        print("[Baseline] all samples skipped by sv_min filter; try smaller sv_min_skip.")
        return dict(
            errs_rel=errs_rel,
            errs_abs=errs_abs,
            kept=0,
            mean_rel=np.nan,
            std_rel=np.nan,
            p90_rel=np.nan,
            mean_abs=np.nan,
            std_abs=np.nan,
            p90_abs=np.nan,
        )

    mean_rel = float(errs_rel.mean())
    std_rel = float(errs_rel.std())
    p90_rel = float(np.percentile(errs_rel, 90))

    mean_abs = float(errs_abs.mean())
    std_abs = float(errs_abs.std())
    p90_abs = float(np.percentile(errs_abs, 90))

    print(
        f"[BASELINE REL] mean={mean_rel:.3e}, std={std_rel:.3e}, "
        f"median={np.median(errs_rel):.3e}, p90={p90_rel:.3e}"
    )
    print(
        f"[BASELINE ABS] mean={mean_abs:.3e}, std={std_abs:.3e}, "
        f"median={np.median(errs_abs):.3e}, p90={p90_abs:.3e}"
    )

    summary = dict(
        errs_rel=errs_rel,
        errs_abs=errs_abs,
        kept=kept,
        mean_rel=mean_rel,
        std_rel=std_rel,
        p90_rel=p90_rel,
        mean_abs=mean_abs,
        std_abs=std_abs,
        p90_abs=p90_abs,
    )
    return summary


def consistency_sweep(model,
                      chest_frame: str = 'slave_shoulder',
                      ee_frame: str = 'slave_ee'):
    """
    对不同阻尼系数 lambda 进行一个小 sweep，查看：
    - 估计扳手范数
    - 回投误差
    - cond(JJ^T)
    """
    est_ref = InteractionEstimator(model, chest_frame, ee_frame, lambda_damp=1e-3)
    q = pin.randomConfiguration(model)
    qd = np.random.randn(model.nv) * 0.05
    qdd = np.zeros(model.nv)

    CJ = est_ref._chest_jacobian(q, qd)
    CF_true = np.array([5.0, -3.0, 8.0, 0.5, 0.2, -0.1])

    M = pin.crba(model, est_ref.data, q)
    M = (M + M.T) - np.diag(M.diagonal())
    nle = pin.nonLinearEffects(model, est_ref.data, q, qd)
    g = pin.computeGeneralizedGravity(model, est_ref.data, q)
    Cqd = nle - g
    tau_model = M @ qdd + Cqd + g
    tau_meas = tau_model + CJ.T @ CF_true

    lambdas = [1e-6, 3e-6, 1e-5, 3e-5, 1e-4, 3e-4, 1e-3, 3e-3, 1e-2]
    print("\n[Lambda sweep]  λ,  ‖F_est‖,  back_err,  cond(JJᵀ)")
    for lam in lambdas:
        est = InteractionEstimator(model, chest_frame, ee_frame, lambda_damp=lam)
        tau_int, CF_est, CJ_use = est.estimate(q, qd, qdd, tau_meas)
        back_err = rel_err(CJ_use.T @ CF_est, tau_int)
        JJt = CJ_use @ CJ_use.T
        cond = np.linalg.cond(JJt) if np.linalg.matrix_rank(JJt) == 6 else np.inf
        print(f"{lam:8.1e}  {np.linalg.norm(CF_est):8.3f}  {back_err:8.2e}  {cond:8.2e}")


def main():
    conf = OmegaConf.load("./config/config.yaml")
    urdf_path = getattr(conf, "slave_urdf", None)
    model = load_model(urdf_path)

    # 注意：chest_frame 在论文中记为 C 框架，这里保持一致
    chest_frame = "slave_base"   # 或 "slave_shoulder"，视你的 URDF 定义而定
    ee_frame = "slave_ee"

    print("\n=== ORACLE (no noise, λ=1e-6) ===")
    summary_ideal = oracle_test(
        model, chest_frame, ee_frame,
        n_trials=300, noise_tau_std=0.0, lambda_damp=1e-6,
        seed=11, sv_min_skip=1e-6
    )

    print("\n=== PROPOSED (tau noise 0.05 N·m, λ=1e-3) ===")
    summary_proposed = oracle_test(
        model, chest_frame, ee_frame,
        n_trials=300, noise_tau_std=0.05, lambda_damp=1e-3,
        seed=13, sv_min_skip=1e-6
    )

    print("\n=== BASELINE (tau noise 0.05 N·m, undamped pseudoinverse) ===")
    summary_baseline = oracle_test_baseline(
        model, chest_frame, ee_frame,
        n_trials=300, noise_tau_std=0.05,
        seed=17, sv_min_skip=1e-6
    )

    # 打印一个 markdown 风格的小表格，方便直接贴到论文
    def to_percent(x):
        return 100.0 * x if x is not None and not np.isnan(x) else float("nan")

    print("\n=== SUMMARY (relative wrench error e_F) ===")
    print("| Method                 | mean e_F [%] | std e_F [%] | p90 e_F [%] | Kept |")
    print("|------------------------|-------------:|------------:|------------:|-----:|")
    print(
        f"| Ideal (no noise)      | {to_percent(summary_ideal['mean_rel']):11.2f} | "
        f"{to_percent(summary_ideal['std_rel']):11.2f} | "
        f"{to_percent(summary_ideal['p90_rel']):11.2f} | "
        f"{summary_ideal['kept']:4d} |"
    )
    print(
        f"| Proposed (damped)     | {to_percent(summary_proposed['mean_rel']):11.2f} | "
        f"{to_percent(summary_proposed['std_rel']):11.2f} | "
        f"{to_percent(summary_proposed['p90_rel']):11.2f} | "
        f"{summary_proposed['kept']:4d} |"
    )
    print(
        f"| Baseline (PI, undamp) | {to_percent(summary_baseline['mean_rel']):11.2f} | "
        f"{to_percent(summary_baseline['std_rel']):11.2f} | "
        f"{to_percent(summary_baseline['p90_rel']):11.2f} | "
        f"{summary_baseline['kept']:4d} |"
    )

    # 可选：扫 lambda，看条件数/回投误差（和论文里 Fig. 6(a)(b) 对应）
    consistency_sweep(model, chest_frame, ee_frame)


if __name__ == "__main__":
    main()
