"""Tests for action manager functionality."""

from unittest.mock import Mock

import pytest
import torch
from conftest import get_test_device

from mjlab.managers.action_manager import ActionManager


@pytest.fixture
def device():
  """Test device fixture."""
  return get_test_device()


def _make_mock_action_term(action_dim: int):
  """Create a mock action term factory."""

  def factory(env):
    term = Mock()
    term.action_dim = action_dim
    term.raw_action = torch.zeros(env.num_envs, action_dim, device=env.device)
    term.process_actions = Mock()
    term.apply_actions = Mock()
    term.reset = Mock()
    return term

  return factory


@pytest.fixture
def mock_env(device):
  """Create a mock environment for testing."""
  env = Mock()
  env.num_envs = 4
  env.device = device
  return env


@pytest.fixture
def action_term_cfg():
  """Create a simple action term config."""
  cfg = Mock()
  cfg.build = _make_mock_action_term(action_dim=3)
  cfg.entity_name = "robot"
  return cfg


def test_action_history_tracking(mock_env, action_term_cfg, device):
  """Test that action, prev_action, and prev_prev_action track history correctly."""
  manager = ActionManager({"action": action_term_cfg}, mock_env)

  # Initial state: all zeros.
  assert torch.all(manager.action == 0.0)
  assert torch.all(manager.prev_action == 0.0)
  assert torch.all(manager.prev_prev_action == 0.0)

  # Process actions and verify history shifts correctly.
  actions = [
    torch.tensor([[float(i)] * 3] * mock_env.num_envs, device=device)
    for i in range(1, 5)
  ]

  manager.process_action(actions[0])
  assert torch.allclose(manager.action, actions[0])
  assert torch.all(manager.prev_action == 0.0)
  assert torch.all(manager.prev_prev_action == 0.0)

  manager.process_action(actions[1])
  assert torch.allclose(manager.action, actions[1])
  assert torch.allclose(manager.prev_action, actions[0])
  assert torch.all(manager.prev_prev_action == 0.0)

  manager.process_action(actions[2])
  assert torch.allclose(manager.action, actions[2])
  assert torch.allclose(manager.prev_action, actions[1])
  assert torch.allclose(manager.prev_prev_action, actions[0])

  manager.process_action(actions[3])
  assert torch.allclose(manager.action, actions[3])
  assert torch.allclose(manager.prev_action, actions[2])
  assert torch.allclose(manager.prev_prev_action, actions[1])


def test_action_history_reset(mock_env, action_term_cfg, device):
  """Test that reset clears action history for all or specific environments."""
  manager = ActionManager({"action": action_term_cfg}, mock_env)

  # Populate history.
  actions = [
    torch.tensor([[float(i)] * 3] * mock_env.num_envs, device=device)
    for i in range(1, 4)
  ]
  for a in actions:
    manager.process_action(a)

  # Partial reset: only envs 0 and 2.
  manager.reset(env_ids=torch.tensor([0, 2]))

  # Reset envs should be zeros.
  for env_id in [0, 2]:
    assert torch.all(manager.action[env_id] == 0.0)
    assert torch.all(manager.prev_action[env_id] == 0.0)
    assert torch.all(manager.prev_prev_action[env_id] == 0.0)

  # Non-reset envs should retain history.
  for env_id in [1, 3]:
    assert torch.allclose(manager.action[env_id], actions[2][env_id])
    assert torch.allclose(manager.prev_action[env_id], actions[1][env_id])
    assert torch.allclose(manager.prev_prev_action[env_id], actions[0][env_id])

  # Full reset.
  manager.reset()
  assert torch.all(manager.action == 0.0)
  assert torch.all(manager.prev_action == 0.0)
  assert torch.all(manager.prev_prev_action == 0.0)
