#!/usr/bin/env python

# Copyright 2025 The HuggingFace Inc. team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from unittest.mock import MagicMock, patch

import pytest

from lerobot.utils.import_utils import is_package_available

if not is_package_available("reachy2_sdk"):
    pytest.skip("reachy2_sdk not available", allow_module_level=True)

from lerobot.teleoperators.reachy2_teleoperator import (
    REACHY2_ANTENNAS_JOINTS,
    REACHY2_L_ARM_JOINTS,
    REACHY2_NECK_JOINTS,
    REACHY2_R_ARM_JOINTS,
    REACHY2_VEL,
    Reachy2Teleoperator,
    Reachy2TeleoperatorConfig,
)

# {lerobot_keys: reachy2_sdk_keys}
REACHY2_JOINTS = {
    **REACHY2_NECK_JOINTS,
    **REACHY2_ANTENNAS_JOINTS,
    **REACHY2_R_ARM_JOINTS,
    **REACHY2_L_ARM_JOINTS,
}

PARAMS = [
    {},  # default config
    {"with_mobile_base": False},
    {"with_mobile_base": False, "with_l_arm": False, "with_antennas": False},
    {"with_r_arm": False, "with_neck": False, "with_antennas": False},
    {"with_mobile_base": False, "with_neck": False},
    {"use_present_position": True},
]


def _make_reachy2_sdk_mock():
    r = MagicMock(name="ReachySDKMock")
    r.is_connected.return_value = True

    def _connect():
        r.is_connected.return_value = True

    def _disconnect():
        r.is_connected.return_value = False

    # Mock joints with some dummy positions
    joints = {
        k: MagicMock(
            present_position=float(i),
            goal_position=float(i) + 0.5,
        )
        for i, k in enumerate(REACHY2_JOINTS.values())
    }
    r.joints = joints

    # Mock mobile base with some dummy odometry
    r.mobile_base = MagicMock()
    r.mobile_base.last_cmd_vel = {
        "vx": -0.2,
        "vy": 0.2,
        "vtheta": 11.0,
    }
    r.mobile_base.odometry = {
        "x": 1.0,
        "y": 2.0,
        "theta": 20.0,
        "vx": 0.1,
        "vy": -0.1,
        "vtheta": 8.0,
    }

    r.connect = MagicMock(side_effect=_connect)
    r.disconnect = MagicMock(side_effect=_disconnect)

    return r


@pytest.fixture(params=PARAMS, ids=lambda p: "default" if not p else ",".join(p.keys()))
def reachy2(request):
    with (
        patch(
            "lerobot.teleoperators.reachy2_teleoperator.reachy2_teleoperator.ReachySDK",
            side_effect=lambda *a, **k: _make_reachy2_sdk_mock(),
        ),
    ):
        overrides = request.param
        cfg = Reachy2TeleoperatorConfig(ip_address="192.168.0.200", **overrides)
        robot = Reachy2Teleoperator(cfg)
        yield robot
        if robot.is_connected:
            robot.disconnect()


def test_connect_disconnect(reachy2):
    assert not reachy2.is_connected

    reachy2.connect()
    assert reachy2.is_connected

    reachy2.disconnect()
    assert not reachy2.is_connected

    reachy2.reachy.disconnect.assert_called_once()


def test_get_action(reachy2):
    reachy2.connect()
    action = reachy2.get_action()

    expected_keys = set(reachy2.joints_dict)
    expected_keys.update(f"{v}" for v in REACHY2_VEL if reachy2.config.with_mobile_base)
    assert set(action.keys()) == expected_keys

    for motor in reachy2.joints_dict:
        if reachy2.config.use_present_position:
            assert action[motor] == reachy2.reachy.joints[REACHY2_JOINTS[motor]].present_position
        else:
            assert action[motor] == reachy2.reachy.joints[REACHY2_JOINTS[motor]].goal_position
    if reachy2.config.with_mobile_base:
        if reachy2.config.use_present_position:
            for vel in REACHY2_VEL:
                assert action[vel] == reachy2.reachy.mobile_base.odometry[REACHY2_VEL[vel]]
        else:
            for vel in REACHY2_VEL:
                assert action[vel] == reachy2.reachy.mobile_base.last_cmd_vel[REACHY2_VEL[vel]]


def test_no_part_declared():
    with pytest.raises(ValueError):
        _ = Reachy2TeleoperatorConfig(
            ip_address="192.168.0.200",
            with_mobile_base=False,
            with_l_arm=False,
            with_r_arm=False,
            with_neck=False,
            with_antennas=False,
        )
