"""Step definitions for tdd_memory_service_entity_persistence.feature (bug #10455).

TDD issue-capture tests verifying that ``EntityStore`` persists entities
across simulated process restarts (separate service instances backed by
the same SQLite database).

Bug #10455: ``EntityStore._load_from_persistence()`` is a stub (``pass``)
and ``_persist_if_needed()`` marks ``dirty=False`` without writing any data.
A fresh ``EntityStore`` instance backed by the same database should contain
entities added by a previous instance.

These steps exercise the current (buggy) behaviour by creating fresh
``EntityStore`` / ``MemoryService`` instances to simulate separate process
invocations.  When the bug is fixed, the service will use a database
repository and fresh instances backed by the same database will share state.
"""

from __future__ import annotations

import tempfile
from pathlib import Path
from typing import Any

from behave import given, then, when

from cleveragents.application.services.memory_service import (
    EntityStore,
    EntityType,
    MemoryService,
)


@given(
    'I create an EntityStore with a SQLite connection string and session "{session_id}"'
)
def step_create_entity_store_with_sqlite(context: Any, session_id: str) -> None:
    """Create an EntityStore backed by a temporary SQLite database."""
    context.entity_store_tmp_dir = tempfile.mkdtemp()
    db_path = Path(context.entity_store_tmp_dir) / "entities.db"
    context.entity_store_connection_string = f"sqlite:///{db_path}"
    context.entity_store_session_id = session_id
    context.entity_store_instance_a = EntityStore(
        session_id=session_id,
        connection_string=context.entity_store_connection_string,
    )


@given(
    'I create a MemoryService with a SQLite connection string and session "{session_id}"'
)
def step_create_memory_service_with_sqlite(context: Any, session_id: str) -> None:
    """Create a MemoryService backed by a temporary SQLite database."""
    context.memory_service_tmp_dir = tempfile.mkdtemp()
    db_path = Path(context.memory_service_tmp_dir) / "memory.db"
    context.memory_service_connection_string = f"sqlite:///{db_path}"
    context.memory_service_session_id = session_id
    context.memory_service_instance_a = MemoryService(
        session_id=session_id,
        connection_string=context.memory_service_connection_string,
    )


@given("I create an EntityStore with an invalid connection string")
def step_create_entity_store_with_invalid_connection(context: Any) -> None:
    """Create an EntityStore with an invalid connection string.

    The EntityStore may raise an exception during __init__ (in _load_from_persistence)
    or during track() (in _persist_if_needed). Either way, an exception should be raised.
    """
    context.persistence_exception: Exception | None = None
    try:
        context.invalid_entity_store = EntityStore(
            session_id="invalid-session",
            connection_string="invalid://not-a-real-db",
        )
    except Exception as exc:
        context.persistence_exception = exc
        context.invalid_entity_store = None


@when('I track a project entity "{name}" in the first EntityStore instance')
def step_track_project_entity_in_store_a(context: Any, name: str) -> None:
    """Track a project entity in the first EntityStore instance."""
    context.entity_store_instance_a.track(name, EntityType.PROJECT)
    context.tracked_entity_name = name
    context.tracked_entity_type = EntityType.PROJECT


@when(
    "I create a fresh EntityStore instance with the same connection string and session"
)
def step_create_fresh_entity_store(context: Any) -> None:
    """Create a fresh EntityStore instance backed by the same database."""
    context.entity_store_instance_b = EntityStore(
        session_id=context.entity_store_session_id,
        connection_string=context.entity_store_connection_string,
    )


@when('I track a plan entity "{name}" via the MemoryService')
def step_track_plan_entity_via_memory_service(context: Any, name: str) -> None:
    """Track a plan entity via the MemoryService."""
    context.memory_service_instance_a.track_entity(name, EntityType.PLAN)
    context.tracked_plan_name = name


@when("I create a fresh MemoryService with the same connection string and session")
def step_create_fresh_memory_service(context: Any) -> None:
    """Create a fresh MemoryService instance backed by the same database."""
    context.memory_service_instance_b = MemoryService(
        session_id=context.memory_service_session_id,
        connection_string=context.memory_service_connection_string,
    )


@when("I attempt to track an entity in the EntityStore with invalid connection")
def step_attempt_track_with_invalid_connection(context: Any) -> None:
    """Attempt to track an entity in the EntityStore with invalid connection.

    If the EntityStore was created successfully (exception not raised in __init__),
    try to track an entity which should raise an exception in _persist_if_needed.
    If the EntityStore couldn't be created, the exception was already captured.
    """
    if (
        context.invalid_entity_store is not None
        and context.persistence_exception is None
    ):
        try:
            context.invalid_entity_store.track("test-entity", EntityType.PROJECT)
            context.persistence_exception = None
        except Exception as exc:
            context.persistence_exception = exc


@when("I track multiple entities in the first EntityStore instance")
def step_track_multiple_entities_in_store_a(context: Any) -> None:
    """Track multiple entities in the first EntityStore instance."""
    context.entity_store_instance_a.track("project-alpha", EntityType.PROJECT)
    context.entity_store_instance_a.track("plan-beta", EntityType.PLAN)
    context.entity_store_instance_a.track("file-gamma.py", EntityType.FILE)
    context.tracked_entities = [
        ("project-alpha", EntityType.PROJECT),
        ("plan-beta", EntityType.PLAN),
        ("file-gamma.py", EntityType.FILE),
    ]


@then('the fresh EntityStore instance should contain the entity "{name}"')
def step_fresh_entity_store_contains_entity(context: Any, name: str) -> None:
    """Assert the fresh EntityStore instance contains the tracked entity."""
    entity = context.entity_store_instance_b.get(
        context.tracked_entity_name, context.tracked_entity_type
    )
    assert entity is not None, (
        f"Expected entity '{name}' to be present in fresh EntityStore instance "
        f"(simulating process restart), but it was not found. "
        f"This confirms bug #10455: EntityStore._load_from_persistence() is a stub."
    )
    assert entity.name == name, f"Expected entity name '{name}' but got '{entity.name}'"


@then('the fresh MemoryService should return the entity "{name}" when queried')
def step_fresh_memory_service_contains_entity(context: Any, name: str) -> None:
    """Assert the fresh MemoryService returns the tracked entity."""
    entity = context.memory_service_instance_b.get_entity(
        context.tracked_plan_name, EntityType.PLAN
    )
    assert entity is not None, (
        f"Expected entity '{name}' to be present in fresh MemoryService instance "
        f"(simulating process restart), but it was not found. "
        f"This confirms bug #10455: EntityStore._persist_if_needed() is a stub."
    )
    assert entity.name == name, f"Expected entity name '{name}' but got '{entity.name}'"


@then("an exception should be raised rather than silently failing")
def step_exception_raised_not_silent(context: Any) -> None:
    """Assert that an exception was raised rather than silently failing."""
    assert context.persistence_exception is not None, (
        "Expected an exception to be raised when persistence fails, "
        "but no exception was raised. "
        "This confirms bug #10455: _persist_if_needed() silently marks dirty=False "
        "without actually persisting data."
    )


@then("all tracked entities should be present in the fresh EntityStore instance")
def step_all_entities_in_fresh_store(context: Any) -> None:
    """Assert all tracked entities are present in the fresh EntityStore instance."""
    for name, entity_type in context.tracked_entities:
        entity = context.entity_store_instance_b.get(name, entity_type)
        assert entity is not None, (
            f"Expected entity '{name}' (type={entity_type.value}) to be present "
            f"in fresh EntityStore instance (simulating process restart), "
            f"but it was not found. "
            f"This confirms bug #10455: EntityStore persistence is not implemented."
        )
        assert entity.name == name, (
            f"Expected entity name '{name}' but got '{entity.name}'"
        )


def _table_to_metadata(table: Any) -> dict[str, str]:
    """Convert a Behave table of key/value rows into a metadata dictionary."""
    if table is None:
        return {}
    metadata: dict[str, str] = {}
    for row in table:
        key = row.get("key")
        value = row.get("value")
        if key is None:
            raise AssertionError("Metadata table is missing a 'key' column entry")
        if value is None:
            raise AssertionError(
                f"Metadata row for key '{key}' is missing a 'value' column entry"
            )
        metadata[key] = value
    return metadata


@when('I track a project entity "{name}" with metadata')
def step_track_project_entity_with_metadata(context: Any, name: str) -> None:
    """Track a project entity with provided metadata in the first EntityStore."""
    metadata = _table_to_metadata(context.table)
    context.entity_store_instance_a.track(name, EntityType.PROJECT, metadata=metadata)
    context.tracked_entity_name = name
    context.tracked_entity_type = EntityType.PROJECT
    context.tracked_metadata = metadata.copy()


@when('I track the same EntityStore entity "{name}" again with metadata')
def step_track_same_entity_again_with_metadata(context: Any, name: str) -> None:
    """Track the same entity again with additional metadata to update persistence."""
    metadata = _table_to_metadata(context.table)
    if getattr(context, "tracked_entity_name", None) != name:
        raise AssertionError(
            "Scenario setup error: attempting to update metadata for a different entity"
        )
    context.entity_store_instance_a.track(name, context.tracked_entity_type, metadata)
    context.tracked_metadata.update(metadata)


@then('the fresh EntityStore entity "{name}" should include metadata')
def step_fresh_entity_should_include_metadata(context: Any, name: str) -> None:
    """Assert that the fresh EntityStore entity has the expected metadata entries."""
    entity = context.entity_store_instance_b.get(name, context.tracked_entity_type)
    assert entity is not None, (
        f"Expected entity '{name}' to be present after restart, but it was missing."
    )
    expected_metadata = _table_to_metadata(context.table)
    for key, value in expected_metadata.items():
        actual_value = entity.metadata.get(key)
        assert actual_value == value, (
            f"Expected metadata key '{key}' to equal '{value}', got '{actual_value}'"
        )


@then('the fresh EntityStore entity "{name}" should have mention count {count:d}')
def step_fresh_entity_should_have_mention_count(
    context: Any, name: str, count: int
) -> None:
    """Assert that the fresh EntityStore entity has the expected mention count."""
    entity = context.entity_store_instance_b.get(name, context.tracked_entity_type)
    assert entity is not None, (
        f"Expected entity '{name}' to be present after restart, but it was missing."
    )
    assert entity.mention_count == count, (
        f"Expected mention count {count}, got {entity.mention_count}"
    )
