import pytest
from unittest.mock import patch, AsyncMock

from talemate.game.engine.context_id import (
    ContextID,
    CharacterDescriptionContextID,
    CharacterAttributeContextID,
    CharacterDetailContextID,
    WorldEntryContextID,
    WorldEntryManualContextID,
    StaticHistoryEntryContextID,
    DynamicHistoryEntryContextID,
    get_context_id_type,
    context_id_from_string,
    context_id_from_object,
    context_id_item_from_string,
    context_id_handler_from_string,
    compress_name,
    CONTEXT_ID_TYPES,
    CONTEXT_ID_PATH_HANDLERS,
    ContextIDHandlerError,
    ContextIDNoHandlerFound,
    CharacterContext,
)
from talemate.character import Character
from talemate.history import HistoryEntry
from talemate.world_state import ManualContext


@pytest.mark.parametrize(
    "path, expected_path_str",
    [
        (["test"], "context:test"),
        (["test", "path"], "context:test.path"),
        (["nested", "test", "path"], "context:nested.test.path"),
        ([], "context:"),
    ],
)
def test_context_id_basic_functionality(path, expected_path_str):
    """Test basic ContextID functionality with various paths."""
    context_id = ContextID(path=path)
    assert context_id.path == path
    assert context_id.context_type == "context"
    assert context_id.path_to_str == expected_path_str
    assert context_id.id == expected_path_str
    assert str(context_id) == expected_path_str


@pytest.mark.parametrize(
    "path",
    [
        ["test"],
        ["test", "path"],
        ["nested", "test", "path"],
    ],
)
def test_context_id_class_methods(path):
    """Test ContextID class methods."""
    context_id_make = ContextID.make(path)

    assert context_id_make.path == path


@pytest.mark.parametrize(
    "character_input, expected_character",
    [
        ("TestChar", "TestChar"),
        (Character(name="RealChar"), "RealChar"),
    ],
)
def test_character_description_context_id(character_input, expected_character):
    """Test CharacterDescriptionContextID creation."""
    context_id = CharacterDescriptionContextID.make(character_input)
    assert context_id.character == expected_character
    assert context_id.path == [expected_character, "description"]
    assert context_id.context_type == "character.description"
    assert (
        context_id.path_to_str
        == f"character.description:{expected_character}.description"
    )


@pytest.mark.parametrize(
    "character_input, attribute, expected_character",
    [
        ("TestChar", "strength", "TestChar"),
        (Character(name="RealChar"), "intelligence", "RealChar"),
        ("Player", "dexterity", "Player"),
    ],
)
def test_character_attribute_context_id(character_input, attribute, expected_character):
    """Test CharacterAttributeContextID creation."""
    context_id = CharacterAttributeContextID.make(character_input, attribute)
    assert context_id.character == expected_character
    assert context_id.attribute == attribute
    # Path should use compressed attribute name
    assert context_id.path == [expected_character, compress_name(attribute)]
    assert context_id.context_type == "character.attribute"


@pytest.mark.parametrize(
    "character_input, detail, expected_character",
    [
        ("TestChar", "appearance", "TestChar"),
        (Character(name="RealChar"), "personality", "RealChar"),
        ("Player", "background", "Player"),
    ],
)
def test_character_detail_context_id(character_input, detail, expected_character):
    """Test CharacterDetailContextID creation."""
    context_id = CharacterDetailContextID.make(character_input, detail)
    assert context_id.character == expected_character
    assert context_id.detail == detail
    # Path should use compressed detail name
    assert context_id.path == [expected_character, compress_name(detail)]
    assert context_id.context_type == "character.detail"


@pytest.mark.parametrize(
    "entry_id, expected_path",
    [
        ("test_entry_123", []),
        ("world_item_456", []),
    ],
)
def test_world_entry_context_id(entry_id, expected_path):
    """Test WorldEntryContextID direct creation (base class)."""
    context_id = WorldEntryContextID(entry_id=entry_id, path=expected_path)
    assert context_id.entry_id == entry_id
    assert context_id.path == expected_path
    assert context_id.context_type == "world_entry"


@pytest.mark.parametrize(
    "entry_input, expected_entry_id",
    [
        ("test_entry", "test_entry"),
        (ManualContext(id="manual_123", text="test text"), "manual_123"),
    ],
)
def test_world_entry_manual_context_id(entry_input, expected_entry_id):
    """Test WorldEntryManualContextID creation with string or ManualContext."""
    context_id = WorldEntryManualContextID.make(entry_input)
    assert context_id.context_type == "world_entry.manual"
    assert context_id.entry_id == expected_entry_id
    # Path should use compressed entry ID
    assert context_id.path == [compress_name(expected_entry_id)]


@pytest.mark.parametrize(
    "entry_id, layer, expected_path",
    [
        ("entry_123", 0, ["layer", "0", "id", "entry_123"]),
        ("entry_456", 5, ["layer", "5", "id", "entry_456"]),
        ("entry_789", 10, ["layer", "10", "id", "entry_789"]),
    ],
)
def test_dynamic_history_entry_context_id(entry_id, layer, expected_path):
    """Test DynamicHistoryEntryContextID creation."""
    entry = HistoryEntry(text="test", ts="PT1S", index=0, layer=layer, id=entry_id)
    context_id = DynamicHistoryEntryContextID.make(entry)
    assert context_id.entry_id == entry_id
    assert context_id.layer == layer
    assert context_id.path == expected_path
    assert context_id.context_type == "history_entry.dynamic"


@pytest.mark.parametrize(
    "entry_id",
    [
        "entry_123",
        "static_entry_456",
    ],
)
def test_static_history_entry_context_id(entry_id):
    """Test StaticHistoryEntryContextID creation."""
    entry = HistoryEntry(text="test", ts="PT1S", index=0, layer=0, id=entry_id)
    context_id = StaticHistoryEntryContextID.make(entry)
    assert context_id.entry_id == entry_id
    assert context_id.path == [entry_id]
    assert context_id.context_type == "history_entry.static"


@pytest.mark.parametrize(
    "context_type, expected_class",
    [
        ("character.description", CharacterDescriptionContextID),
        ("character.attribute", CharacterAttributeContextID),
        ("character.detail", CharacterDetailContextID),
        ("world_entry.manual", WorldEntryManualContextID),
        ("history_entry.static", StaticHistoryEntryContextID),
        ("history_entry.dynamic", DynamicHistoryEntryContextID),
    ],
)
def test_get_context_id_type(context_type, expected_class):
    """Test getting context ID type by string."""
    cls = get_context_id_type(context_type)
    assert cls == expected_class


@pytest.mark.parametrize(
    "context_type",
    [
        "character.description",
        "character.attribute",
        "character.detail",
        "world_entry.manual",
        "history_entry",
        "history_entry.static",
        "history_entry.dynamic",
    ],
)
def test_context_id_types_registration(context_type):
    """Test that context types are properly registered."""
    assert context_type in CONTEXT_ID_TYPES


def test_get_context_id_type_missing():
    """Test that getting non-existent context type raises KeyError."""
    with pytest.raises(KeyError):
        get_context_id_type("nonexistent.type")


@pytest.mark.parametrize(
    "context_type, object_input, expected_type, expected_attrs",
    [
        (
            "character.description",
            "TestChar",
            CharacterDescriptionContextID,
            {"character": "TestChar", "path": ["TestChar", "description"]},
        ),
        (
            "character.description",
            Character(name="RealChar"),
            CharacterDescriptionContextID,
            {"character": "RealChar", "path": ["RealChar", "description"]},
        ),
        (
            "world_entry.manual",
            "manual_entry_123",
            WorldEntryManualContextID,
            {
                "entry_id": "manual_entry_123",
                "path": [compress_name("manual_entry_123")],
            },
        ),
        (
            "world_entry.manual",
            ManualContext(id="ctx_456", text="test context"),
            WorldEntryManualContextID,
            {"entry_id": "ctx_456", "path": [compress_name("ctx_456")]},
        ),
        (
            "history_entry.static",
            HistoryEntry(text="test", ts="PT1S", index=0, layer=0, id="hist_123"),
            StaticHistoryEntryContextID,
            {"entry_id": "hist_123", "path": ["hist_123"]},
        ),
        (
            "history_entry.dynamic",
            HistoryEntry(text="test", ts="PT1S", index=0, layer=3, id="hist_456"),
            DynamicHistoryEntryContextID,
            {
                "entry_id": "hist_456",
                "layer": 3,
                "path": ["layer", "3", "id", "hist_456"],
            },
        ),
    ],
)
def test_context_id_from_object(
    context_type, object_input, expected_type, expected_attrs
):
    """Test creating context IDs from objects using context_id_from_object."""
    context_id = context_id_from_object(context_type, object_input)
    assert isinstance(context_id, expected_type)

    for attr, expected_value in expected_attrs.items():
        assert getattr(context_id, attr) == expected_value


@pytest.mark.parametrize(
    "context_type, object_input, kwargs, expected_type, expected_attrs",
    [
        (
            "character.attribute",
            "TestChar",
            {"attribute": "strength"},
            CharacterAttributeContextID,
            {
                "character": "TestChar",
                "attribute": "strength",
                "path": ["TestChar", compress_name("strength")],
            },
        ),
        (
            "character.attribute",
            Character(name="RealChar"),
            {"attribute": "intelligence"},
            CharacterAttributeContextID,
            {
                "character": "RealChar",
                "attribute": "intelligence",
                "path": ["RealChar", compress_name("intelligence")],
            },
        ),
        (
            "character.detail",
            "TestChar",
            {"detail": "appearance"},
            CharacterDetailContextID,
            {
                "character": "TestChar",
                "detail": "appearance",
                "path": ["TestChar", compress_name("appearance")],
            },
        ),
        (
            "character.detail",
            Character(name="RealChar"),
            {"detail": "personality"},
            CharacterDetailContextID,
            {
                "character": "RealChar",
                "detail": "personality",
                "path": ["RealChar", compress_name("personality")],
            },
        ),
    ],
)
def test_context_id_from_object_with_kwargs(
    context_type, object_input, kwargs, expected_type, expected_attrs
):
    """Test context_id_from_object with kwargs for context types that require multiple arguments."""
    context_id = context_id_from_object(context_type, object_input, **kwargs)
    assert isinstance(context_id, expected_type)

    for attr, expected_value in expected_attrs.items():
        assert getattr(context_id, attr) == expected_value


def test_context_id_from_object_comprehensive():
    """Test that context_id_from_object works for all registered context types."""
    # Test all single-argument context types
    char_desc = context_id_from_object("character.description", "TestChar")
    assert isinstance(char_desc, CharacterDescriptionContextID)

    manual_entry = context_id_from_object("world_entry.manual", "test_entry")
    assert isinstance(manual_entry, WorldEntryManualContextID)

    static_hist = context_id_from_object(
        "history_entry.static",
        HistoryEntry(text="test", ts="PT1S", index=0, layer=0, id="test_123"),
    )
    assert isinstance(static_hist, StaticHistoryEntryContextID)
    assert static_hist.path == ["test_123"]

    dynamic_hist = context_id_from_object(
        "history_entry.dynamic",
        HistoryEntry(text="test", ts="PT1S", index=0, layer=2, id="test_456"),
    )
    assert isinstance(dynamic_hist, DynamicHistoryEntryContextID)

    # Test multi-argument context types with kwargs
    char_attr = context_id_from_object(
        "character.attribute", "TestChar", attribute="strength"
    )
    assert isinstance(char_attr, CharacterAttributeContextID)
    assert char_attr.attribute == "strength"

    char_detail = context_id_from_object(
        "character.detail", "TestChar", detail="appearance"
    )
    assert isinstance(char_detail, CharacterDetailContextID)
    assert char_detail.detail == "appearance"


@pytest.mark.parametrize(
    "context_id, expected_str",
    [
        (ContextID(path=["test", "path"]), "context:test.path"),
        (
            CharacterDescriptionContextID(
                character="TestChar", path=["TestChar", "description"]
            ),
            "character.description:TestChar.description",
        ),
        (
            CharacterAttributeContextID(
                character="Player",
                attribute="strength",
                path=["Player", compress_name("strength")],
            ),
            f"character.attribute:Player.{compress_name('strength')}",
        ),
        (
            CharacterDetailContextID(
                character="Hero",
                detail="appearance",
                path=["Hero", compress_name("appearance")],
            ),
            f"character.detail:Hero.{compress_name('appearance')}",
        ),
        (
            WorldEntryContextID(entry_id="world_123", path=["world_123"]),
            "world_entry:world_123",
        ),
        (
            WorldEntryManualContextID(
                entry_id="manual_456", path=[compress_name("manual_456")]
            ),
            f"world_entry.manual:{compress_name('manual_456')}",
        ),
        (
            StaticHistoryEntryContextID(entry_id="hist_789", path=["hist_789"]),
            "history_entry.static:hist_789",
        ),
        (
            DynamicHistoryEntryContextID(
                entry_id="dyn_abc", layer=3, path=["layer", "3", "id", "dyn_abc"]
            ),
            "history_entry.dynamic:layer.3.id.dyn_abc",
        ),
    ],
)
def test_context_id_str_method(context_id, expected_str):
    """Test that __str__ method works correctly for all context ID types."""
    assert str(context_id) == expected_str
    # Also verify that it's consistent with id property
    assert str(context_id) == context_id.id
    assert str(context_id) == context_id.path_to_str


def test_context_id_string_formatting():
    """Test that context IDs work correctly in string formatting contexts."""
    context_id = CharacterDescriptionContextID(
        character="TestChar", path=["TestChar", "description"]
    )

    # Test f-string formatting
    formatted = f"Context: {context_id}"
    assert formatted == "Context: character.description:TestChar.description"

    # Test string concatenation
    concatenated = "Prefix-" + str(context_id) + "-Suffix"
    assert concatenated == "Prefix-character.description:TestChar.description-Suffix"

    # Test in collections that might call __str__
    context_list = [context_id]
    assert str(context_list[0]) == "character.description:TestChar.description"


def test_context_id_from_object_invalid_context_type():
    """Test context_id_from_object with invalid context type."""
    with pytest.raises(KeyError):
        context_id_from_object("nonexistent.type", "test_object")


# Tests for Character Context Handler and Context ID Value Flow


class MockScene:
    """Mock scene for testing context handlers."""

    def __init__(self):
        self.characters = {}

    def get_character(self, name: str) -> Character:
        return self.characters.get(name)

    def add_character(self, character: Character):
        self.characters[character.name] = character


@pytest.fixture
def mock_scene():
    """Create a mock scene with test characters."""
    scene = MockScene()

    # Create test character with attributes and details
    char = Character(
        name="TestCharacter",
        description="A brave warrior",
        base_attributes={"strength": 15, "intelligence": 12, "dexterity": 14},
        details={
            "appearance": "Tall and muscular",
            "personality": "Brave and loyal",
            "background": "Former knight",
        },
    )
    scene.add_character(char)

    # Create second character for testing
    char2 = Character(
        name="Wizard",
        description="A wise spellcaster",
        base_attributes={"intelligence": 18, "wisdom": 16},
        details={"appearance": "Old with a long beard", "specialty": "Fire magic"},
    )
    scene.add_character(char2)

    return scene


def test_character_context_handler_registration():
    """Test that CharacterContext handler is properly registered."""

    # Verify handler is registered for all expected context types
    expected_types = [
        "character.description",
        "character.attribute",
        "character.detail",
    ]

    for context_type in expected_types:
        assert context_type in CONTEXT_ID_PATH_HANDLERS
        assert CONTEXT_ID_PATH_HANDLERS[context_type] == CharacterContext


def test_character_context_instance_from_path(mock_scene):
    """Test CharacterContext.instance_from_path method."""

    # Test successful character lookup
    handler = CharacterContext.instance_from_path(["TestCharacter"], mock_scene)
    assert handler is not None
    assert handler.character.name == "TestCharacter"

    # Test with non-existent character should raise ContextIDHandlerError
    with pytest.raises(
        ContextIDHandlerError, match="Character 'NonExistent' not found in scene"
    ):
        CharacterContext.instance_from_path(["NonExistent"], mock_scene)


def test_character_context_properties(mock_scene):
    """Test CharacterContext property accessors."""

    handler = CharacterContext.instance_from_path(["TestCharacter"], mock_scene)

    # Test description property
    desc = handler.description
    assert desc.context_type == "description"
    assert desc.character.name == "TestCharacter"
    assert desc.name == "description"
    assert desc.value == "A brave warrior"

    # Test attributes generator
    attributes = list(handler.attributes)
    assert len(attributes) == 3
    attr_names = [attr.name for attr in attributes]
    assert "strength" in attr_names
    assert "intelligence" in attr_names
    assert "dexterity" in attr_names

    for attr in attributes:
        assert attr.context_type == "attribute"
        assert attr.character.name == "TestCharacter"

    # Test details generator
    details = list(handler.details)
    assert len(details) == 3
    detail_names = [detail.name for detail in details]
    assert "appearance" in detail_names
    assert "personality" in detail_names
    assert "background" in detail_names

    for detail in details:
        assert detail.context_type == "detail"
        assert detail.character.name == "TestCharacter"


def test_character_context_get_methods(mock_scene):
    """Test CharacterContext.get_attribute and get_detail methods."""

    handler = CharacterContext.instance_from_path(["TestCharacter"], mock_scene)

    # Test get_attribute
    strength = handler.get_attribute("strength")
    assert strength is not None
    assert strength.name == "strength"
    assert strength.value == 15

    # Test get_attribute for non-existent attribute
    missing = handler.get_attribute("charisma")
    assert missing is None

    # Test get_detail
    appearance = handler.get_detail("appearance")
    assert appearance is not None
    assert appearance.name == "appearance"
    assert appearance.value == "Tall and muscular"

    # Test get_detail for non-existent detail
    missing = handler.get_detail("missing_detail")
    assert missing is None


@pytest.mark.asyncio
async def test_character_context_item_operations(mock_scene):
    """Test CharacterContextItem get and set operations."""

    handler = CharacterContext.instance_from_path(["TestCharacter"], mock_scene)
    character = handler.character

    # Mock the memory agent since we don't have a real one in tests
    mock_memory_agent = AsyncMock()
    with patch("talemate.instance.get_agent", return_value=mock_memory_agent):
        # Test description operations
        desc_item = handler.description
        value = await desc_item.get(mock_scene)
        assert value == "A brave warrior"

        await desc_item.set(mock_scene, "Updated description")
        assert character.description == "Updated description"

        # Test attribute operations
        strength_item = handler.get_attribute("strength")
        value = await strength_item.get(mock_scene)
        assert value == 15

        await strength_item.set(mock_scene, 18)
        assert character.base_attributes["strength"] == 18

        # Test detail operations
        appearance_item = handler.get_detail("appearance")
        value = await appearance_item.get(mock_scene)
        assert value == "Tall and muscular"

        await appearance_item.set(mock_scene, "Short and thin")
        assert character.details["appearance"] == "Short and thin"


def test_character_context_item_context_id_property(mock_scene):
    """Test that CharacterContextItem.context_id returns correct ContextID types."""

    handler = CharacterContext.instance_from_path(["TestCharacter"], mock_scene)

    # Test description context_id
    desc_item = handler.description
    context_id = desc_item.context_id
    assert isinstance(context_id, CharacterDescriptionContextID)
    assert context_id.character == "TestCharacter"

    # Test attribute context_id
    strength_item = handler.get_attribute("strength")
    context_id = strength_item.context_id
    assert isinstance(context_id, CharacterAttributeContextID)
    assert context_id.character == "TestCharacter"
    assert context_id.attribute == "strength"

    # Test detail context_id
    appearance_item = handler.get_detail("appearance")
    context_id = appearance_item.context_id
    assert isinstance(context_id, CharacterDetailContextID)
    assert context_id.character == "TestCharacter"
    assert context_id.detail == "appearance"


def test_character_context_item_human_id_property(mock_scene):
    """Test that CharacterContextItem.human_id returns human-readable descriptions."""

    handler = CharacterContext.instance_from_path(["TestCharacter"], mock_scene)

    # Test description human_id
    desc_item = handler.description
    assert desc_item.human_id == "Information about TestCharacter - 'description'"

    # Test attribute human_id
    strength_item = handler.get_attribute("strength")
    assert strength_item.human_id == "Information about TestCharacter - 'strength'"

    # Test detail human_id
    appearance_item = handler.get_detail("appearance")
    assert appearance_item.human_id == "Information about TestCharacter - 'appearance'"


def test_character_context_compressed_path(mock_scene):
    """Test that CharacterContextItem.compressed_path returns correct path strings."""

    handler = CharacterContext.instance_from_path(["TestCharacter"], mock_scene)

    # Test description compressed path
    desc_item = handler.description
    assert (
        desc_item.compressed_path == "character.description:TestCharacter.description"
    )

    # Test attribute compressed path
    strength_item = handler.get_attribute("strength")
    expected_path = f"character.attribute:TestCharacter.{compress_name('strength')}"
    assert strength_item.compressed_path == expected_path

    # Test detail compressed path
    appearance_item = handler.get_detail("appearance")
    expected_path = f"character.detail:TestCharacter.{compress_name('appearance')}"
    assert appearance_item.compressed_path == expected_path


@pytest.mark.asyncio
async def test_character_context_id_item_from_path(mock_scene):
    """Test CharacterContext.context_id_item_from_path method."""

    # Create handler instance first
    handler = CharacterContext.instance_from_path(["TestCharacter"], mock_scene)

    # Test description context
    desc_value = await handler.context_id_item_from_path(
        "character.description",
        ["TestCharacter", "description"],
        "character.description:TestCharacter.description",
        mock_scene,
    )
    assert desc_value is not None
    assert desc_value.context_type == "description"
    assert desc_value.character.name == "TestCharacter"

    # Test attribute context
    strength_compressed = compress_name("strength")
    attr_path_str = f"character.attribute:TestCharacter.{strength_compressed}"
    attr_value = await handler.context_id_item_from_path(
        "character.attribute",
        ["TestCharacter", strength_compressed],
        attr_path_str,
        mock_scene,
    )
    assert attr_value is not None
    assert attr_value.context_type == "attribute"
    assert attr_value.name == "strength"
    assert attr_value.value == 15

    # Test detail context
    appearance_compressed = compress_name("appearance")
    detail_path_str = f"character.detail:TestCharacter.{appearance_compressed}"
    detail_value = await handler.context_id_item_from_path(
        "character.detail",
        ["TestCharacter", appearance_compressed],
        detail_path_str,
        mock_scene,
    )
    assert detail_value is not None
    assert detail_value.context_type == "detail"
    assert detail_value.name == "appearance"
    assert detail_value.value == "Tall and muscular"

    # Test non-existent character should raise ContextIDHandlerError

    with pytest.raises(
        ContextIDHandlerError, match="Character 'NonExistent' not found in scene"
    ):
        non_existent_handler = CharacterContext.instance_from_path(
            ["NonExistent"], mock_scene
        )
        await non_existent_handler.context_id_item_from_path(
            "character.description",
            ["NonExistent", "description"],
            "character.description:NonExistent.description",
            mock_scene,
        )

    # Test invalid context type
    none_value = await handler.context_id_item_from_path(
        "invalid.type",
        ["TestCharacter", "description"],
        "invalid.type:TestCharacter.description",
        mock_scene,
    )
    assert none_value is None


@pytest.mark.asyncio
async def test_character_context_id_from_path(mock_scene):
    """Test CharacterContext.context_id_from_path method."""

    # Test description context ID
    handler = CharacterContext.instance_from_path(["TestCharacter"], mock_scene)
    desc_context_id = await handler.context_id_from_path(
        "character.description",
        ["TestCharacter", "description"],
        "character.description:TestCharacter.description",
        mock_scene,
    )
    assert desc_context_id is not None
    assert isinstance(desc_context_id, CharacterDescriptionContextID)
    assert desc_context_id.character == "TestCharacter"

    # Test attribute context ID
    strength_compressed = compress_name("strength")
    attr_path_str = f"character.attribute:TestCharacter.{strength_compressed}"
    attr_context_id = await handler.context_id_from_path(
        "character.attribute",
        ["TestCharacter", strength_compressed],
        attr_path_str,
        mock_scene,
    )
    assert attr_context_id is not None
    assert isinstance(attr_context_id, CharacterAttributeContextID)
    assert attr_context_id.character == "TestCharacter"
    assert attr_context_id.attribute == "strength"

    # Test detail context ID
    appearance_compressed = compress_name("appearance")
    detail_path_str = f"character.detail:TestCharacter.{appearance_compressed}"
    detail_context_id = await handler.context_id_from_path(
        "character.detail",
        ["TestCharacter", appearance_compressed],
        detail_path_str,
        mock_scene,
    )
    assert detail_context_id is not None
    assert isinstance(detail_context_id, CharacterDetailContextID)
    assert detail_context_id.character == "TestCharacter"
    assert detail_context_id.detail == "appearance"

    # Test non-existent character should raise ContextIDHandlerError
    with pytest.raises(
        ContextIDHandlerError, match="Character 'NonExistent' not found in scene"
    ):
        non_existent_handler = CharacterContext.instance_from_path(
            ["NonExistent"], mock_scene
        )
        await non_existent_handler.context_id_from_path(
            "character.description",
            ["NonExistent", "description"],
            "character.description:NonExistent.description",
            mock_scene,
        )


def test_context_id_handler_lookup(mock_scene):
    """Test context_id_handler_from_string function."""

    # Test valid character context types
    for context_type in [
        "character.description",
        "character.attribute",
        "character.detail",
    ]:
        context_id_str = f"{context_type}:TestCharacter.test"
        handler = context_id_handler_from_string(context_id_str, mock_scene)
        assert isinstance(handler, CharacterContext)

    # Test invalid context type
    with pytest.raises(ContextIDNoHandlerFound):
        context_id_handler_from_string("nonexistent.type:test.path", mock_scene)


@pytest.mark.asyncio
async def test_integration_context_id_item_flow(mock_scene):
    """Test the complete context ID value flow from string to value operations."""

    # Test functions directly with mock_scene parameter

    # Test description flow
    desc_context_id_str = "character.description:TestCharacter.description"

    # Test context_id_item_from_string
    context_value = await context_id_item_from_string(desc_context_id_str, mock_scene)
    assert context_value is not None
    assert context_value.context_type == "description"
    assert context_value.character.name == "TestCharacter"
    assert context_value.value == "A brave warrior"

    # Test context_id_from_string
    context_id = await context_id_from_string(desc_context_id_str, mock_scene)
    assert context_id is not None
    assert isinstance(context_id, CharacterDescriptionContextID)
    assert context_id.character == "TestCharacter"

    # Test attribute flow
    strength_compressed = compress_name("strength")
    attr_context_id_str = f"character.attribute:TestCharacter.{strength_compressed}"

    context_value = await context_id_item_from_string(attr_context_id_str, mock_scene)
    assert context_value is not None
    assert context_value.context_type == "attribute"
    assert context_value.name == "strength"
    assert context_value.value == 15

    context_id = await context_id_from_string(attr_context_id_str, mock_scene)
    assert context_id is not None
    assert isinstance(context_id, CharacterAttributeContextID)
    assert context_id.attribute == "strength"

    # Test detail flow
    appearance_compressed = compress_name("appearance")
    detail_context_id_str = f"character.detail:TestCharacter.{appearance_compressed}"

    context_value = await context_id_item_from_string(detail_context_id_str, mock_scene)
    assert context_value is not None
    assert context_value.context_type == "detail"
    assert context_value.name == "appearance"
    assert context_value.value == "Tall and muscular"

    context_id = await context_id_from_string(detail_context_id_str, mock_scene)
    assert context_id is not None
    assert isinstance(context_id, CharacterDetailContextID)
    assert context_id.detail == "appearance"


@pytest.mark.asyncio
async def test_integration_context_id_item_flow_no_scene():
    """Test context ID value flow when no active scene is available."""

    # Create empty scene with no characters
    empty_scene = MockScene()

    desc_context_id_str = "character.description:TestCharacter.description"

    # Should raise ContextIDHandlerError when character not found
    with pytest.raises(
        ContextIDHandlerError, match="Character 'TestCharacter' not found in scene"
    ):
        await context_id_item_from_string(desc_context_id_str, empty_scene)

    with pytest.raises(
        ContextIDHandlerError, match="Character 'TestCharacter' not found in scene"
    ):
        await context_id_from_string(desc_context_id_str, empty_scene)


@pytest.mark.asyncio
async def test_integration_context_id_item_flow_nonexistent_character(mock_scene):
    """Test context ID value flow with nonexistent character."""

    # Test with nonexistent character should raise ContextIDHandlerError
    desc_context_id_str = "character.description:NonExistent.description"

    with pytest.raises(
        ContextIDHandlerError, match="Character 'NonExistent' not found in scene"
    ):
        await context_id_item_from_string(desc_context_id_str, mock_scene)

    with pytest.raises(
        ContextIDHandlerError, match="Character 'NonExistent' not found in scene"
    ):
        await context_id_from_string(desc_context_id_str, mock_scene)


@pytest.mark.asyncio
async def test_integration_full_context_value_operations(mock_scene):
    """Test full integration of context value operations through the context ID system."""

    # Mock memory agent
    mock_memory_agent = AsyncMock()
    with patch("talemate.instance.get_agent", return_value=mock_memory_agent):
        # Get context value for character description
        desc_context_id_str = "character.description:TestCharacter.description"
        context_value = await context_id_item_from_string(
            desc_context_id_str, mock_scene
        )
        assert context_value is not None

        # Test get operation
        original_value = await context_value.get(mock_scene)
        assert original_value == "A brave warrior"

        # Test set operation
        await context_value.set(mock_scene, "A legendary warrior")
        updated_value = await context_value.get(mock_scene)
        assert updated_value == "A legendary warrior"

        # Verify character was actually updated
        character = mock_scene.get_character("TestCharacter")
        assert character.description == "A legendary warrior"

        # Test attribute operations
        strength_compressed = compress_name("strength")
        attr_context_id_str = f"character.attribute:TestCharacter.{strength_compressed}"
        attr_context_value = await context_id_item_from_string(
            attr_context_id_str, mock_scene
        )
        assert attr_context_value is not None

        original_strength = await attr_context_value.get(mock_scene)
        assert original_strength == 15

        await attr_context_value.set(mock_scene, 20)
        updated_strength = await attr_context_value.get(mock_scene)
        assert updated_strength == 20
        assert character.base_attributes["strength"] == 20

        # Test detail operations
        appearance_compressed = compress_name("appearance")
        detail_context_id_str = (
            f"character.detail:TestCharacter.{appearance_compressed}"
        )
        detail_context_value = await context_id_item_from_string(
            detail_context_id_str, mock_scene
        )
        assert detail_context_value is not None

        original_appearance = await detail_context_value.get(mock_scene)
        assert original_appearance == "Tall and muscular"

        await detail_context_value.set(mock_scene, "Imposing and battle-scarred")
        updated_appearance = await detail_context_value.get(mock_scene)
        assert updated_appearance == "Imposing and battle-scarred"
        assert character.details["appearance"] == "Imposing and battle-scarred"


def test_character_context_edge_cases(mock_scene):
    """Test edge cases for character context operations."""

    # Test with empty character name should raise ContextIDHandlerError
    with pytest.raises(ContextIDHandlerError, match="Character '' not found in scene"):
        CharacterContext.instance_from_path([""], mock_scene)

    # Test with character that has no attributes or details
    empty_char = Character(name="EmptyChar", description="Empty character")
    mock_scene.add_character(empty_char)

    handler = CharacterContext.instance_from_path(["EmptyChar"], mock_scene)
    assert handler is not None

    # Should return empty lists for attributes and details
    attributes = list(handler.attributes)
    details = list(handler.details)
    assert len(attributes) == 0
    assert len(details) == 0

    # Description should still work
    desc_item = handler.description
    assert desc_item.value == "Empty character"

    # get_attribute and get_detail should return None for non-existent items
    assert handler.get_attribute("nonexistent") is None
    assert handler.get_detail("nonexistent") is None
