"""
Integration tests for circular dependency detection.

Tests cycle detection with real DB, pre-validation before
persisting edges, and error detail reporting.
"""

import pytest
import tempfile
from pathlib import Path

from markitect.prompts.models import Artifact, ArtifactType
from markitect.prompts.repositories.sqlite import SQLiteArtifactRepository
from markitect.prompts.dependencies.models import (
    DependencyEdge,
    EdgeType,
    CircularDependencyError,
)
from markitect.prompts.dependencies.repository import SQLiteDependencyRepository
from markitect.prompts.dependencies.graph import GraphBuilder
from markitect.prompts.dependencies.queries import DependencyQueryService


@pytest.fixture
def temp_db():
    """Create temporary database for testing."""
    with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f:
        db_path = f.name
    yield db_path
    Path(db_path).unlink(missing_ok=True)


@pytest.fixture
def artifact_repo(temp_db):
    """Create artifact repository."""
    return SQLiteArtifactRepository(temp_db)


@pytest.fixture
def dep_repo(temp_db):
    """Create dependency repository."""
    return SQLiteDependencyRepository(temp_db)


@pytest.fixture
def builder(dep_repo):
    """Create GraphBuilder."""
    return GraphBuilder(dep_repo)


@pytest.fixture
def query_service(dep_repo):
    """Create DependencyQueryService."""
    return DependencyQueryService(dep_repo)


def _create_artifact(artifact_repo, space_id, name):
    """Helper to create and persist an artifact."""
    artifact = Artifact.create(
        space_id=space_id,
        name=name,
        content=f"content for {name}",
        artifact_type=ArtifactType.CONTENT,
    )
    return artifact_repo.create(artifact)


def _persist_edge(dep_repo, src_id, tgt_id, run_id="run-1"):
    """Helper to persist a dependency edge."""
    edge = DependencyEdge.create(
        source_artifact_id=src_id,
        target_artifact_id=tgt_id,
        run_id=run_id,
        edge_type=EdgeType.REQUIRES,
    )
    return dep_repo.create(edge)


class TestCycleDetectionWithDB:
    """Tests for cycle detection using real database storage."""

    def test_detect_simple_cycle(self, artifact_repo, dep_repo, query_service):
        """Test detecting a simple 2-node cycle in persisted graph."""
        art_a = _create_artifact(artifact_repo, "space-1", "a")
        art_b = _create_artifact(artifact_repo, "space-1", "b")

        _persist_edge(dep_repo, art_a.id, art_b.id)
        _persist_edge(dep_repo, art_b.id, art_a.id, run_id="run-2")

        cycles = query_service.detect_circular_dependencies()
        assert len(cycles) > 0

    def test_detect_three_node_cycle(self, artifact_repo, dep_repo, query_service):
        """Test detecting a 3-node cycle A -> B -> C -> A."""
        art_a = _create_artifact(artifact_repo, "space-1", "a")
        art_b = _create_artifact(artifact_repo, "space-1", "b")
        art_c = _create_artifact(artifact_repo, "space-1", "c")

        _persist_edge(dep_repo, art_a.id, art_b.id)
        _persist_edge(dep_repo, art_b.id, art_c.id)
        _persist_edge(dep_repo, art_c.id, art_a.id, run_id="run-2")

        cycles = query_service.detect_circular_dependencies()
        assert len(cycles) > 0

        # All three artifacts should be in the cycle
        cycle_nodes = set(cycles[0][:-1])
        assert art_a.id in cycle_nodes
        assert art_b.id in cycle_nodes
        assert art_c.id in cycle_nodes

    def test_no_false_positives(self, artifact_repo, dep_repo, query_service):
        """Test no false positive cycle detection in DAG."""
        art_a = _create_artifact(artifact_repo, "space-1", "a")
        art_b = _create_artifact(artifact_repo, "space-1", "b")
        art_c = _create_artifact(artifact_repo, "space-1", "c")
        art_d = _create_artifact(artifact_repo, "space-1", "d")

        # Diamond: A -> B, A -> C, B -> D, C -> D
        _persist_edge(dep_repo, art_a.id, art_b.id)
        _persist_edge(dep_repo, art_a.id, art_c.id, run_id="run-2")
        _persist_edge(dep_repo, art_b.id, art_d.id, run_id="run-3")
        _persist_edge(dep_repo, art_c.id, art_d.id, run_id="run-4")

        cycles = query_service.detect_circular_dependencies()
        assert cycles == []


class TestPreValidation:
    """Tests for pre-validation (would_create_cycle) before persisting edges."""

    def test_validate_safe_edge(self, artifact_repo, dep_repo, query_service):
        """Test pre-validation accepts safe edge."""
        art_a = _create_artifact(artifact_repo, "space-1", "a")
        art_b = _create_artifact(artifact_repo, "space-1", "b")
        art_c = _create_artifact(artifact_repo, "space-1", "c")

        _persist_edge(dep_repo, art_a.id, art_b.id)

        # B -> C would not create a cycle
        assert query_service.would_create_cycle(art_b.id, art_c.id) is False

    def test_validate_cycle_creating_edge(
        self, artifact_repo, dep_repo, query_service
    ):
        """Test pre-validation rejects edge that would create a cycle."""
        art_a = _create_artifact(artifact_repo, "space-1", "a")
        art_b = _create_artifact(artifact_repo, "space-1", "b")
        art_c = _create_artifact(artifact_repo, "space-1", "c")

        _persist_edge(dep_repo, art_a.id, art_b.id)
        _persist_edge(dep_repo, art_b.id, art_c.id, run_id="run-2")

        # C -> A would create a cycle
        assert query_service.would_create_cycle(art_c.id, art_a.id) is True

    def test_validate_before_persist_workflow(
        self, artifact_repo, dep_repo, query_service
    ):
        """Test the full validate-then-persist workflow."""
        art_a = _create_artifact(artifact_repo, "space-1", "a")
        art_b = _create_artifact(artifact_repo, "space-1", "b")
        art_c = _create_artifact(artifact_repo, "space-1", "c")

        # Persist A -> B
        _persist_edge(dep_repo, art_a.id, art_b.id)

        # Validate and persist B -> C (safe)
        assert query_service.would_create_cycle(art_b.id, art_c.id) is False
        _persist_edge(dep_repo, art_b.id, art_c.id, run_id="run-2")

        # Validate C -> A (would create cycle, don't persist)
        assert query_service.would_create_cycle(art_c.id, art_a.id) is True
        # Don't persist - graph remains acyclic

        # Verify graph is still cycle-free
        assert query_service.detect_circular_dependencies() == []


class TestCycleErrorDetails:
    """Tests for cycle error detail reporting."""

    def test_topological_sort_error_contains_cycle(
        self, artifact_repo, dep_repo, builder
    ):
        """Test that topological sort error includes cycle details."""
        art_a = _create_artifact(artifact_repo, "space-1", "a")
        art_b = _create_artifact(artifact_repo, "space-1", "b")
        art_c = _create_artifact(artifact_repo, "space-1", "c")

        _persist_edge(dep_repo, art_a.id, art_b.id)
        _persist_edge(dep_repo, art_b.id, art_c.id, run_id="run-2")
        _persist_edge(dep_repo, art_c.id, art_a.id, run_id="run-3")

        graph = builder.build_graph()

        with pytest.raises(CircularDependencyError) as exc_info:
            graph.topological_sort()

        error = exc_info.value
        assert error.cycle is not None
        assert len(error.cycle) >= 3
        # The cycle should contain our artifact IDs
        cycle_set = set(error.cycle)
        assert art_a.id in cycle_set
        assert art_b.id in cycle_set
        assert art_c.id in cycle_set

    def test_error_message_readable(self, artifact_repo, dep_repo, builder):
        """Test that error message is human-readable."""
        art_a = _create_artifact(artifact_repo, "space-1", "a")
        art_b = _create_artifact(artifact_repo, "space-1", "b")

        _persist_edge(dep_repo, art_a.id, art_b.id)
        _persist_edge(dep_repo, art_b.id, art_a.id, run_id="run-2")

        graph = builder.build_graph()

        with pytest.raises(CircularDependencyError) as exc_info:
            graph.topological_sort()

        message = str(exc_info.value)
        assert "Circular dependency detected" in message
        assert "->" in message


class TestCrossSpaceCycles:
    """Tests for cycle detection across information spaces."""

    def test_cross_space_cycle(self, artifact_repo, dep_repo, query_service):
        """Test detecting cycles that span multiple spaces."""
        # space-a/X -> space-b/Y -> space-a/X (cycle across spaces)
        art_x = _create_artifact(artifact_repo, "space-a", "x")
        art_y = _create_artifact(artifact_repo, "space-b", "y")

        _persist_edge(dep_repo, art_x.id, art_y.id)
        _persist_edge(dep_repo, art_y.id, art_x.id, run_id="run-2")

        cycles = query_service.detect_circular_dependencies()
        assert len(cycles) > 0

    def test_cross_space_transitive_cycle(
        self, artifact_repo, dep_repo, query_service
    ):
        """Test detecting transitive cycles across multiple spaces."""
        art_a = _create_artifact(artifact_repo, "space-a", "a")
        art_b = _create_artifact(artifact_repo, "space-b", "b")
        art_c = _create_artifact(artifact_repo, "space-c", "c")

        _persist_edge(dep_repo, art_a.id, art_b.id)
        _persist_edge(dep_repo, art_b.id, art_c.id, run_id="run-2")
        _persist_edge(dep_repo, art_c.id, art_a.id, run_id="run-3")

        cycles = query_service.detect_circular_dependencies()
        assert len(cycles) > 0

        # Pre-validation should also catch it
        # If we had A -> B -> C already, adding C -> A would be caught
        assert query_service.would_create_cycle(art_c.id, art_a.id) is True

    def test_cross_space_no_false_positive(
        self, artifact_repo, dep_repo, query_service
    ):
        """Test no false positives across spaces."""
        art_a = _create_artifact(artifact_repo, "space-a", "a")
        art_b = _create_artifact(artifact_repo, "space-b", "b")
        art_c = _create_artifact(artifact_repo, "space-c", "c")

        # Linear: A -> B -> C (no cycle)
        _persist_edge(dep_repo, art_a.id, art_b.id)
        _persist_edge(dep_repo, art_b.id, art_c.id, run_id="run-2")

        cycles = query_service.detect_circular_dependencies()
        assert cycles == []
