import pytest

from myfasthtml.core.instances import (
  BaseInstance,
  SingleInstance,
  MultipleInstance,
  InstancesManager,
  DuplicateInstanceError,
  special_session,
  Ids,
  RootInstance
)


@pytest.fixture(autouse=True)
def reset_instances():
  """Reset instances before each test to ensure isolation."""
  InstancesManager.instances.clear()
  yield
  InstancesManager.instances.clear()


@pytest.fixture
def session():
  """Create a test session."""
  return {"user_info": {"id": "test-user-123"}}


@pytest.fixture
def another_session():
  """Create another test session."""
  return {"user_info": {"id": "test-user-456"}}


@pytest.fixture
def root_instance(session):
  """Create a root instance for testing."""
  return SingleInstance(parent=None, session=session, _id="test-root")


# Example subclasses for testing
class SubSingleInstance(SingleInstance):
  """Example subclass of SingleInstance with simplified signature."""
  
  def __init__(self, parent):
    super().__init__(parent=parent)


class SubMultipleInstance(MultipleInstance):
  """Example subclass of MultipleInstance with custom parameter."""
  
  def __init__(self, parent, _id=None, custom_param=None):
    super().__init__(parent=parent, _id=_id)
    self.custom_param = custom_param


class TestBaseInstance:
  
  def test_i_can_create_a_base_instance_with_positional_args(self, session, root_instance):
    """Test that a BaseInstance can be created with positional arguments."""
    instance = BaseInstance(root_instance, session, "test_id")
    
    assert instance is not None
    assert instance.get_id() == "test_id"
    assert instance.get_session() == session
    assert instance.get_parent() == root_instance
  
  def test_i_can_create_a_base_instance_with_kwargs(self, session, root_instance):
    """Test that a BaseInstance can be created with keyword arguments."""
    instance = BaseInstance(parent=root_instance, session=session, _id="test_id")
    
    assert instance is not None
    assert instance.get_id() == "test_id"
    assert instance.get_session() == session
    assert instance.get_parent() == root_instance
  
  def test_i_can_create_a_base_instance_with_mixed_args(self, session, root_instance):
    """Test that a BaseInstance can be created with mixed positional and keyword arguments."""
    instance = BaseInstance(root_instance, session=session, _id="test_id")
    
    assert instance is not None
    assert instance.get_id() == "test_id"
    assert instance.get_session() == session
    assert instance.get_parent() == root_instance
  
  def test_i_can_retrieve_the_same_instance_when_using_same_session_and_id(self, session, root_instance):
    """Test that creating an instance with same session and id returns the existing instance."""
    instance1 = BaseInstance(root_instance, session, "same_id")
    instance2 = BaseInstance(root_instance, session, "same_id")
    
    assert instance1 is instance2
  
  def test_i_can_control_instances_registration(self, session, root_instance):
    """Test that auto_register=False prevents automatic registration."""
    BaseInstance(parent=root_instance, session=session, _id="test_id", auto_register=False)
    
    session_id = InstancesManager.get_session_id(session)
    key = (session_id, "test_id")
    
    assert key not in InstancesManager.instances
  
  def test_i_can_have_different_instances_for_different_sessions(self, session, another_session, root_instance):
    """Test that different sessions can have instances with the same id."""
    root_instance2 = SingleInstance(parent=None, session=another_session, _id="test-root")
    
    instance1 = BaseInstance(root_instance, session, "same_id")
    instance2 = BaseInstance(root_instance2, another_session, "same_id")
    
    assert instance1 is not instance2
    assert instance1.get_session() == session
    assert instance2.get_session() == another_session
  
  def test_i_can_create_instance_with_parent_only(self, session, root_instance):
    """Test that session can be extracted from parent when not provided."""
    instance = BaseInstance(parent=root_instance, _id="test_id")
    
    assert instance.get_session() == root_instance.get_session()
    assert instance.get_parent() == root_instance
  
  def test_i_cannot_create_instance_without_parent_or_session(self):
    """Test that creating an instance without parent or session raises TypeError."""
    with pytest.raises(TypeError, match="Either session or parent must be provided"):
      BaseInstance(None, _id="test_id")
  
  def test_i_can_get_auto_generated_id(self, session, root_instance):
    """Test that if _id is not provided, an ID is auto-generated via compute_id()."""
    instance = BaseInstance(parent=root_instance, session=session)
    
    assert instance.get_id() is not None
    assert instance.get_id().startswith("mf-base_instance-")
  
  def test_i_can_get_prefix_from_class_name(self):
    """Test that get_prefix() returns the correct snake_case prefix."""
    prefix = BaseInstance.get_prefix()
    
    assert prefix == "mf-base_instance"


class TestSingleInstance:
  
  def test_i_can_create_a_single_instance(self, session, root_instance):
    """Test that a SingleInstance can be created."""
    instance = SingleInstance(parent=root_instance, session=session)
    
    assert instance is not None
    assert instance.get_id() == "mf-single_instance"
    assert instance.get_session() == session
    assert instance.get_parent() == root_instance
  
  def test_i_can_create_single_instance_with_positional_args(self, session, root_instance):
    """Test that a SingleInstance can be created with positional arguments."""
    instance = SingleInstance(root_instance, session, "custom_id")
    
    assert instance is not None
    assert instance.get_id() == "custom_id"
    assert instance.get_session() == session
    assert instance.get_parent() == root_instance
  
  def test_the_same_instance_is_returned(self, session):
    """Test that single instance is cached and returned on subsequent calls."""
    instance1 = SingleInstance(parent=None, session=session, _id="unique_id")
    instance2 = SingleInstance(parent=None, session=session, _id="unique_id")
    
    assert instance1 is instance2
  
  def test_i_cannot_create_duplicate_single_instance(self, session):
    """Test that creating a duplicate SingleInstance raises DuplicateInstanceError."""
    instance = SingleInstance(parent=None, session=session, _id="unique_id")
    
    with pytest.raises(DuplicateInstanceError):
      InstancesManager.register(session, instance)
  
  def test_i_can_retrieve_existing_single_instance(self, session):
    """Test that attempting to create an existing SingleInstance returns the same instance."""
    instance1 = SingleInstance(parent=None, session=session, _id="same_id")
    instance2 = SingleInstance(parent=None, session=session, _id="same_id", auto_register=False)
    
    assert instance1 is instance2
  
  def test_i_can_get_auto_computed_id_for_single_instance(self, session):
    """Test that the default ID equals prefix for SingleInstance."""
    instance = SingleInstance(parent=None, session=session)
    
    assert instance.get_id() == "mf-single_instance"
    assert instance.get_id() == SingleInstance.get_prefix()


class TestSingleInstanceSubclass:
  
  def test_i_can_create_subclass_of_single_instance(self, root_instance):
    """Test that a subclass of SingleInstance works correctly."""
    instance = SubSingleInstance(root_instance)
    
    assert instance is not None
    assert isinstance(instance, SingleInstance)
    assert isinstance(instance, SubSingleInstance)
  
  def test_i_can_create_subclass_with_custom_signature(self, root_instance):
    """Test that subclass with simplified signature works correctly."""
    instance = SubSingleInstance(root_instance)
    
    assert instance.get_parent() == root_instance
    assert instance.get_session() == root_instance.get_session()
    assert instance.get_id() == "mf-sub_single_instance"
    assert instance.get_prefix() == "mf-sub_single_instance"
  
  def test_i_can_retrieve_subclass_instance_from_cache(self, root_instance):
    """Test that cache works for subclasses."""
    instance1 = SubSingleInstance(root_instance)
    instance2 = SubSingleInstance(root_instance)
    
    assert instance1 is instance2


class TestMultipleInstance:
  
  def test_i_can_create_multiple_instances_with_same_prefix(self, session, root_instance):
    """Test that multiple MultipleInstance objects can be created with the same prefix."""
    instance1 = MultipleInstance(parent=root_instance, session=session)
    instance2 = MultipleInstance(parent=root_instance, session=session)
    
    assert instance1 is not instance2
    assert instance1.get_id() != instance2.get_id()
    assert instance1.get_id().startswith("mf-multiple_instance-")
    assert instance2.get_id().startswith("mf-multiple_instance-")
  
  def test_i_can_have_auto_generated_unique_ids(self, session, root_instance):
    """Test that each MultipleInstance receives a unique auto-generated ID."""
    instances = [MultipleInstance(parent=root_instance, session=session) for _ in range(5)]
    ids = [inst.get_id() for inst in instances]
    
    # All IDs should be unique
    assert len(ids) == len(set(ids))
    
    # All IDs should start with the prefix
    assert all(id.startswith("mf-multiple_instance-") for id in ids)
  
  def test_i_can_provide_custom_id_to_multiple_instance(self, session, root_instance):
    """Test that a custom _id can be provided to MultipleInstance."""
    custom_id = "custom-instance-id"
    instance = MultipleInstance(parent=root_instance, session=session, _id=custom_id)
    
    assert instance.get_id() == custom_id
  
  def test_i_can_retrieve_multiple_instance_by_custom_id(self, session, root_instance):
    """Test that a MultipleInstance with custom _id can be retrieved from cache."""
    custom_id = "custom-instance-id"
    instance1 = MultipleInstance(parent=root_instance, session=session, _id=custom_id)
    instance2 = MultipleInstance(parent=root_instance, session=session, _id=custom_id)
    
    assert instance1 is instance2


class TestMultipleInstanceSubclass:
  
  def test_i_can_create_subclass_of_multiple_instance(self, root_instance):
    """Test that a subclass of MultipleInstance works correctly."""
    instance = SubMultipleInstance(root_instance, custom_param="test")
    
    assert instance is not None
    assert isinstance(instance, MultipleInstance)
    assert isinstance(instance, SubMultipleInstance)
    assert instance.custom_param == "test"
  
  def test_i_can_create_multiple_subclass_instances_with_auto_generated_ids(self, root_instance):
    """Test that multiple instances of subclass can be created with unique IDs."""
    instance1 = SubMultipleInstance(root_instance, custom_param="first")
    instance2 = SubMultipleInstance(root_instance, custom_param="second")
    
    assert instance1 is not instance2
    assert instance1.get_id() != instance2.get_id()
    assert instance1.get_id().startswith("mf-sub_multiple_instance-")
    assert instance2.get_id().startswith("mf-sub_multiple_instance-")
  
  def test_i_can_create_subclass_with_custom_signature(self, root_instance):
    """Test that subclass with custom parameters works correctly."""
    instance = SubMultipleInstance(root_instance, custom_param="value")
    
    assert instance.get_parent() == root_instance
    assert instance.get_session() == root_instance.get_session()
    assert instance.custom_param == "value"
  
  def test_i_can_retrieve_subclass_instance_from_cache(self, root_instance):
    """Test that cache works for subclasses."""
    instance1 = SubMultipleInstance(root_instance, custom_param="first")
    instance2 = SubMultipleInstance(root_instance, custom_param="second", _id=instance1.get_id())
    
    assert instance1 is instance2
  
  def test_i_cannot_retrieve_subclass_instance_when_type_differs(self, root_instance):
    """Test that cache works for subclasses with custom _id."""
    # Need to pass _id explicitly to enable caching
    instance1 = SubMultipleInstance(root_instance)
    
    with pytest.raises(TypeError):
      MultipleInstance(parent=root_instance, _id=instance1.get_id())
  
  def test_i_can_get_correct_prefix_for_multiple_subclass(self):
    """Test that subclass has correct auto-generated prefix."""
    prefix = SubMultipleInstance.get_prefix()
    
    assert prefix == "mf-sub_multiple_instance"


class TestInstancesManager:
  
  def test_i_can_register_an_instance_manually(self, session, root_instance):
    """Test that an instance can be manually registered."""
    instance = BaseInstance(parent=root_instance, session=session, _id="manual_id", auto_register=False)
    
    InstancesManager.register(session, instance)
    
    session_id = InstancesManager.get_session_id(session)
    key = (session_id, "manual_id")
    
    assert key in InstancesManager.instances
    assert InstancesManager.instances[key] is instance
  
  def test_i_can_get_existing_instance_by_id(self, session, root_instance):
    """Test that an existing instance can be retrieved by ID."""
    instance = BaseInstance(parent=root_instance, session=session, _id="get_id")
    
    retrieved = InstancesManager.get(session, "get_id")
    
    assert retrieved is instance
  
  def test_i_cannot_get_nonexistent_instance_without_type(self, session):
    """Test that getting a non-existent instance without type raises KeyError."""
    with pytest.raises(KeyError):
      InstancesManager.get(session, "nonexistent_id")
  
  def test_i_can_get_session_id_from_valid_session(self, session):
    """Test that session ID is correctly extracted from a valid session."""
    session_id = InstancesManager.get_session_id(session)
    
    assert session_id == "test-user-123"
  
  def test_i_can_handle_none_session(self):
    """Test that None session returns a special identifier."""
    session_id = InstancesManager.get_session_id(None)
    
    assert session_id == "** NOT LOGGED IN **"
  
  def test_i_can_handle_invalid_session(self):
    """Test that invalid sessions return appropriate identifiers."""
    # Session is None
    session_id = InstancesManager.get_session_id(None)
    assert session_id == "** NOT LOGGED IN **"
    
    # Session without user_info
    session_no_user = {}
    session_id = InstancesManager.get_session_id(session_no_user)
    assert session_id == "** UNKNOWN USER **"
    
    # Session with user_info but no id
    session_no_id = {"user_info": {}}
    session_id = InstancesManager.get_session_id(session_no_id)
    assert session_id == "** INVALID SESSION **"
  
  def test_i_can_reset_all_instances(self, session, root_instance):
    """Test that reset() clears all instances."""
    BaseInstance(parent=root_instance, session=session, _id="id1")
    BaseInstance(parent=root_instance, session=session, _id="id2")
    
    assert len(InstancesManager.instances) > 0
    
    InstancesManager.reset()
    
    assert len(InstancesManager.instances) == 0


class TestRootInstance:
  
  def test_i_can_create_root_instance_with_positional_args(self):
    """Test that RootInstance can be created with positional arguments."""
    root = SingleInstance(None, special_session, Ids.Root)
    
    assert root is not None
    assert root.get_id() == Ids.Root
    assert root.get_session() == special_session
    assert root.get_parent() is None
  
  def test_i_can_access_root_instance(self):
    """Test that RootInstance is created and accessible."""
    assert RootInstance is not None
    assert RootInstance.get_id() == Ids.Root
    assert RootInstance.get_session() == special_session
