from __future__ import annotations
import pytest
from src.hot_reloader import HotModule, HotReloader
from unittest.mock import MagicMock, patch
import sys
import types

def test_hot_module_dataclass_fields():
 hm = HotModule(
  name="test_module",
  file_path="/path/to/test_module.py",
  state_keys=["attr1", "attr2"],
  delegation_targets=["method_a", "method_b"],
 )
 assert hm.name == "test_module"
 assert hm.file_path == "/path/to/test_module.py"
 assert hm.state_keys == ["attr1", "attr2"]
 assert hm.delegation_targets == ["method_a", "method_b"]

def test_hot_reloader_register_and_get():
 HotReloader.HOT_MODULES.clear()
 hm = HotModule(name="test_mod", file_path="/fake/path.py", state_keys=[], delegation_targets=[])
 HotReloader.register(hm)
 assert "test_mod" in HotReloader.HOT_MODULES
 assert HotReloader.HOT_MODULES["test_mod"] is hm

def test_hot_reloader_register_duplicate_raises():
 HotReloader.HOT_MODULES.clear()
 hm1 = HotModule(name="dup", file_path="/a.py", state_keys=[], delegation_targets=[])
 HotReloader.register(hm1)
 with pytest.raises(ValueError, match="already registered"):
  HotReloader.register(hm1)

def test_hot_reloader_is_error_state():
 HotReloader.HOT_MODULES.clear()
 HotReloader.last_error = None
 HotReloader.is_error_state = False
 assert HotReloader.is_error_state is False

def test_reload_unknown_module_returns_false():
 HotReloader.HOT_MODULES.clear()
 HotReloader.register(HotModule(name="nonexistent_mod", file_path="/nonexistent.py", state_keys=[], delegation_targets=[]))
 app = MagicMock()
 result = HotReloader.reload("nonexistent_mod", app)
 assert result is False
 assert HotReloader.is_error_state is True
 assert HotReloader.last_error is not None

def test_reload_success_clears_error_state():
 HotReloader.HOT_MODULES.clear()
 test_mod = types.ModuleType("src._test_reload_mod_src")
 sys.modules["src._test_reload_mod_src"] = test_mod
 HotReloader.register(HotModule(name="src._test_reload_mod_src", file_path="/fake.py", state_keys=[], delegation_targets=[]))
 app = MagicMock()
 HotReloader.is_error_state = True
 HotReloader.last_error = "previous error"
 with patch("importlib.reload", return_value=test_mod):
  result = HotReloader.reload("src._test_reload_mod_src", app)
 assert result is True
 assert HotReloader.is_error_state is False
 assert HotReloader.last_error is None
 del sys.modules["src._test_reload_mod_src"]

def test_reload_captures_and_restores_state_on_failure():
 HotReloader.HOT_MODULES.clear()
 HotReloader.register(HotModule(name="bad_mod", file_path="/bad.py", state_keys=["_test_attr"], delegation_targets=[]))
 app = MagicMock()
 app._test_attr = "preserved_value"
 result = HotReloader.reload("bad_mod", app)
 assert result is False
 assert HotReloader.is_error_state is True
 assert app._test_attr == "preserved_value"

def test_reload_all_success():
 HotReloader.HOT_MODULES.clear()
 mod1 = types.ModuleType("hr_test_mod1")
 mod2 = types.ModuleType("hr_test_mod2")
 sys.modules["hr_test_mod1"] = mod1
 sys.modules["hr_test_mod2"] = mod2
 HotReloader.register(HotModule(name="hr_test_mod1", file_path="/fake1.py", state_keys=[], delegation_targets=[]))
 HotReloader.register(HotModule(name="hr_test_mod2", file_path="/fake2.py", state_keys=[], delegation_targets=[]))
 app = MagicMock()
 with patch("importlib.reload", return_value=mod1):
  result = HotReloader.reload_all(app)
 assert result is True
 assert HotReloader.is_error_state is False
 del sys.modules["hr_test_mod1"]
 del sys.modules["hr_test_mod2"]

def test_reload_all_partial_failure():
 HotReloader.HOT_MODULES.clear()
 mod1 = types.ModuleType("hr_test_mod1")
 sys.modules["hr_test_mod1"] = mod1
 HotReloader.register(HotModule(name="hr_test_mod1", file_path="/fake1.py", state_keys=[], delegation_targets=[]))
 HotReloader.register(HotModule(name="hr_nonexistent", file_path="/nonexistent.py", state_keys=[], delegation_targets=[]))
 app = MagicMock()
 result = HotReloader.reload_all(app)
 assert result is False
 assert HotReloader.is_error_state is True
 del sys.modules["hr_test_mod1"]