"""
Integration tests for event propagation in SpaceService.

Tests that events are correctly emitted for all space operations.
"""

import pytest
import tempfile
import os
from unittest.mock import Mock

from markitect.spaces import (
    SpaceService,
    SqliteSpaceRepository,
    SqliteDocumentRepository,
    SqliteVariableRepository,
    SqliteReferenceRepository,
)
from markitect.spaces.events import (
    EventBus,
    SpaceEvent,
    SpaceEventType,
)


@pytest.fixture
def temp_db():
    """Create a temporary database file for testing."""
    fd, path = tempfile.mkstemp(suffix=".db")
    os.close(fd)
    yield path
    if os.path.exists(path):
        os.unlink(path)


@pytest.fixture
def event_bus():
    """Create an EventBus for testing."""
    return EventBus()


@pytest.fixture
def space_service(temp_db, event_bus):
    """Create a SpaceService with EventBus for testing."""
    return SpaceService(
        space_repo=SqliteSpaceRepository(temp_db),
        document_repo=SqliteDocumentRepository(temp_db),
        variable_repo=SqliteVariableRepository(temp_db),
        reference_repo=SqliteReferenceRepository(temp_db),
        event_bus=event_bus,
    )


class TestSpaceEvents:
    """Tests for space lifecycle events."""

    def test_space_created_event(self, space_service, event_bus):
        """Test that SPACE_CREATED event is emitted."""
        received_events = []
        event_bus.subscribe(SpaceEventType.SPACE_CREATED, received_events.append)

        space = space_service.create_space(name="test-space")

        assert len(received_events) == 1
        event = received_events[0]
        assert event.event_type == SpaceEventType.SPACE_CREATED
        assert event.space_id == space.id
        assert event.payload["name"] == "test-space"

    def test_space_updated_event(self, space_service, event_bus):
        """Test that SPACE_UPDATED event is emitted."""
        space = space_service.create_space(name="original")

        received_events = []
        event_bus.subscribe(SpaceEventType.SPACE_UPDATED, received_events.append)

        space_service.update_space(space.id, name="updated", description="New desc")

        assert len(received_events) == 1
        event = received_events[0]
        assert event.event_type == SpaceEventType.SPACE_UPDATED
        assert event.space_id == space.id
        assert event.payload["changes"]["name"] == "updated"
        assert event.payload["changes"]["description"] == "New desc"

    def test_space_deleted_event(self, space_service, event_bus):
        """Test that SPACE_DELETED event is emitted."""
        space = space_service.create_space(name="to-delete")

        received_events = []
        event_bus.subscribe(SpaceEventType.SPACE_DELETED, received_events.append)

        space_service.delete_space(space.id)

        assert len(received_events) == 1
        event = received_events[0]
        assert event.event_type == SpaceEventType.SPACE_DELETED
        assert event.space_id == space.id
        assert event.payload["name"] == "to-delete"

    def test_space_activated_event(self, space_service, event_bus):
        """Test that SPACE_ACTIVATED event is emitted."""
        space = space_service.create_space(name="to-activate")

        received_events = []
        event_bus.subscribe(SpaceEventType.SPACE_ACTIVATED, received_events.append)

        space_service.activate_space(space.id)

        assert len(received_events) == 1
        event = received_events[0]
        assert event.event_type == SpaceEventType.SPACE_ACTIVATED
        assert event.space_id == space.id

    def test_space_archived_event(self, space_service, event_bus):
        """Test that SPACE_ARCHIVED event is emitted."""
        space = space_service.create_space(name="to-archive")

        received_events = []
        event_bus.subscribe(SpaceEventType.SPACE_ARCHIVED, received_events.append)

        space_service.archive_space(space.id)

        assert len(received_events) == 1
        event = received_events[0]
        assert event.event_type == SpaceEventType.SPACE_ARCHIVED
        assert event.space_id == space.id

    def test_cascade_delete_emits_multiple_events(self, space_service, event_bus):
        """Test that cascade delete emits events for all deleted spaces."""
        parent = space_service.create_space(name="parent")
        child1 = space_service.create_space(name="child1", parent_space_id=parent.id)
        child2 = space_service.create_space(name="child2", parent_space_id=parent.id)

        received_events = []
        event_bus.subscribe(SpaceEventType.SPACE_DELETED, received_events.append)

        space_service.delete_space(parent.id, cascade=True)

        # Should have events for parent and both children
        assert len(received_events) == 3
        deleted_ids = {e.space_id for e in received_events}
        assert parent.id in deleted_ids
        assert child1.id in deleted_ids
        assert child2.id in deleted_ids


class TestDocumentEvents:
    """Tests for document events."""

    def test_document_added_event(self, space_service, event_bus):
        """Test that DOCUMENT_ADDED event is emitted."""
        space = space_service.create_space(name="doc-space")

        received_events = []
        event_bus.subscribe(SpaceEventType.DOCUMENT_ADDED, received_events.append)

        doc = space_service.add_document(space.id, "/intro.md")

        assert len(received_events) == 1
        event = received_events[0]
        assert event.event_type == SpaceEventType.DOCUMENT_ADDED
        assert event.space_id == space.id
        assert event.payload["document_id"] == doc.id
        assert event.payload["space_path"] == "/intro.md"

    def test_document_removed_event(self, space_service, event_bus):
        """Test that DOCUMENT_REMOVED event is emitted."""
        space = space_service.create_space(name="doc-space")
        doc = space_service.add_document(space.id, "/to-remove.md")

        received_events = []
        event_bus.subscribe(SpaceEventType.DOCUMENT_REMOVED, received_events.append)

        space_service.remove_document(doc.id)

        assert len(received_events) == 1
        event = received_events[0]
        assert event.event_type == SpaceEventType.DOCUMENT_REMOVED
        assert event.space_id == space.id
        assert event.payload["document_id"] == doc.id
        assert event.payload["space_path"] == "/to-remove.md"

    def test_document_moved_event(self, space_service, event_bus):
        """Test that DOCUMENT_MOVED event is emitted."""
        space = space_service.create_space(name="doc-space")
        doc = space_service.add_document(space.id, "/old-path.md")

        received_events = []
        event_bus.subscribe(SpaceEventType.DOCUMENT_MOVED, received_events.append)

        space_service.move_document(doc.id, "/new-path.md")

        assert len(received_events) == 1
        event = received_events[0]
        assert event.event_type == SpaceEventType.DOCUMENT_MOVED
        assert event.space_id == space.id
        assert event.payload["old_path"] == "/old-path.md"
        assert event.payload["new_path"] == "/new-path.md"

    def test_document_content_changed_event(self, space_service, event_bus):
        """Test that DOCUMENT_CONTENT_CHANGED event is emitted."""
        space = space_service.create_space(name="doc-space")
        doc = space_service.add_document(
            space.id, "/content.md", content_hash="hash-v1"
        )

        received_events = []
        event_bus.subscribe(
            SpaceEventType.DOCUMENT_CONTENT_CHANGED, received_events.append
        )

        space_service.update_document_hash(doc.id, "hash-v2")

        assert len(received_events) == 1
        event = received_events[0]
        assert event.event_type == SpaceEventType.DOCUMENT_CONTENT_CHANGED
        assert event.space_id == space.id
        assert event.payload["old_hash"] == "hash-v1"
        assert event.payload["new_hash"] == "hash-v2"

    def test_no_event_if_hash_unchanged(self, space_service, event_bus):
        """Test that no event is emitted if hash is unchanged."""
        space = space_service.create_space(name="doc-space")
        doc = space_service.add_document(
            space.id, "/content.md", content_hash="same-hash"
        )

        received_events = []
        event_bus.subscribe(
            SpaceEventType.DOCUMENT_CONTENT_CHANGED, received_events.append
        )

        space_service.update_document_hash(doc.id, "same-hash")

        assert len(received_events) == 0


class TestVariableEvents:
    """Tests for variable events."""

    def test_variable_set_event(self, space_service, event_bus):
        """Test that VARIABLE_SET event is emitted."""
        space = space_service.create_space(name="var-space")

        received_events = []
        event_bus.subscribe(SpaceEventType.VARIABLE_SET, received_events.append)

        space_service.set_variable(space.id, "version", "1.0.0")

        assert len(received_events) == 1
        event = received_events[0]
        assert event.event_type == SpaceEventType.VARIABLE_SET
        assert event.space_id == space.id
        assert event.payload["name"] == "version"

    def test_variable_deleted_event(self, space_service, event_bus):
        """Test that VARIABLE_DELETED event is emitted."""
        space = space_service.create_space(name="var-space")
        space_service.set_variable(space.id, "temp", "value")

        received_events = []
        event_bus.subscribe(SpaceEventType.VARIABLE_DELETED, received_events.append)

        space_service.delete_variable(space.id, "temp")

        assert len(received_events) == 1
        event = received_events[0]
        assert event.event_type == SpaceEventType.VARIABLE_DELETED
        assert event.space_id == space.id
        assert event.payload["name"] == "temp"

    def test_no_event_if_variable_not_found(self, space_service, event_bus):
        """Test that no event is emitted if variable doesn't exist."""
        space = space_service.create_space(name="var-space")

        received_events = []
        event_bus.subscribe(SpaceEventType.VARIABLE_DELETED, received_events.append)

        space_service.delete_variable(space.id, "non-existent")

        assert len(received_events) == 0


class TestEventHandlerIntegration:
    """Tests for event handler integration patterns."""

    def test_global_handler_receives_all_events(self, space_service, event_bus):
        """Test that a global handler receives all event types."""
        received_events = []
        event_bus.subscribe_all(received_events.append)

        space = space_service.create_space(name="test")
        space_service.add_document(space.id, "/doc.md")
        space_service.set_variable(space.id, "var", "value")
        space_service.delete_space(space.id)

        # Should have: SPACE_CREATED, DOCUMENT_ADDED, VARIABLE_SET, SPACE_DELETED
        assert len(received_events) >= 4

        event_types = {e.event_type for e in received_events}
        assert SpaceEventType.SPACE_CREATED in event_types
        assert SpaceEventType.DOCUMENT_ADDED in event_types
        assert SpaceEventType.VARIABLE_SET in event_types
        assert SpaceEventType.SPACE_DELETED in event_types

    def test_event_history_records_all_events(self, space_service, event_bus):
        """Test that event history captures all events."""
        event_bus.enable_history()

        space = space_service.create_space(name="history-test")
        space_service.add_document(space.id, "/doc1.md")
        space_service.add_document(space.id, "/doc2.md")
        space_service.activate_space(space.id)

        history = event_bus.get_history()
        assert len(history) == 4

        # Newest first
        assert history[0].event_type == SpaceEventType.SPACE_ACTIVATED
        assert history[1].event_type == SpaceEventType.DOCUMENT_ADDED
        assert history[2].event_type == SpaceEventType.DOCUMENT_ADDED
        assert history[3].event_type == SpaceEventType.SPACE_CREATED

    def test_filter_history_by_space(self, space_service, event_bus):
        """Test filtering event history by space ID."""
        event_bus.enable_history()

        space1 = space_service.create_space(name="space1")
        space2 = space_service.create_space(name="space2")
        space_service.add_document(space1.id, "/doc.md")
        space_service.add_document(space2.id, "/doc.md")

        history_space1 = event_bus.get_history(space_id=space1.id)
        assert len(history_space1) == 2  # create + add_document

        history_space2 = event_bus.get_history(space_id=space2.id)
        assert len(history_space2) == 2

    def test_multiple_handlers_for_same_event(self, space_service, event_bus):
        """Test that multiple handlers can listen to the same event."""
        handler1_events = []
        handler2_events = []

        event_bus.subscribe(SpaceEventType.SPACE_CREATED, handler1_events.append)
        event_bus.subscribe(SpaceEventType.SPACE_CREATED, handler2_events.append)

        space_service.create_space(name="multi-handler-test")

        assert len(handler1_events) == 1
        assert len(handler2_events) == 1
        assert handler1_events[0].event_id == handler2_events[0].event_id


class TestEventDrivenWorkflow:
    """Tests for event-driven workflow patterns."""

    def test_cache_invalidation_pattern(self, space_service, event_bus):
        """Test a cache invalidation pattern using events."""
        cache = {}

        def on_content_changed(event: SpaceEvent):
            doc_id = event.payload["document_id"]
            if doc_id in cache:
                del cache[doc_id]

        event_bus.subscribe(
            SpaceEventType.DOCUMENT_CONTENT_CHANGED, on_content_changed
        )

        # Setup
        space = space_service.create_space(name="cache-test")
        doc = space_service.add_document(space.id, "/doc.md", content_hash="v1")

        # Simulate cached content
        cache[doc.id] = "cached content"

        # Update triggers cache invalidation via event
        space_service.update_document_hash(doc.id, "v2")

        assert doc.id not in cache

    def test_audit_log_pattern(self, space_service, event_bus):
        """Test an audit log pattern using events."""
        audit_log = []

        def log_event(event: SpaceEvent):
            audit_log.append({
                "event_type": event.event_type.value,
                "space_id": event.space_id,
                "timestamp": event.timestamp.isoformat(),
            })

        event_bus.subscribe_all(log_event)

        space = space_service.create_space(name="audit-test")
        space_service.activate_space(space.id)
        space_service.archive_space(space.id)
        space_service.delete_space(space.id)

        # Should have complete audit trail
        assert len(audit_log) == 4
        event_types = [entry["event_type"] for entry in audit_log]
        assert "space.created" in event_types
        assert "space.activated" in event_types
        assert "space.archived" in event_types
        assert "space.deleted" in event_types
