"""
Unit tests for DependencyQueryService.

Tests direct/transitive dependents and dependencies, dependency chains,
cycle validation, would_create_cycle, and build order.
"""

import pytest
import tempfile
from pathlib import Path

from markitect.prompts.dependencies.models import (
    DependencyEdge,
    DependencyGraph,
    EdgeType,
    CircularDependencyError,
)
from markitect.prompts.dependencies.repository import SQLiteDependencyRepository
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 repository(temp_db):
    """Create repository instance with temp database."""
    return SQLiteDependencyRepository(temp_db)


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


def _create_edge(repository, src, tgt, run_id="run-1", edge_type=EdgeType.REQUIRES):
    """Helper to create and persist a dependency edge."""
    edge = DependencyEdge.create(
        source_artifact_id=src,
        target_artifact_id=tgt,
        run_id=run_id,
        edge_type=edge_type,
    )
    return repository.create(edge)


class TestFindDependents:
    """Tests for find_dependents (who depends on X)."""

    def test_find_direct_dependents(self, repository, query_service):
        """Test finding direct dependents of an artifact."""
        _create_edge(repository, "A", "B")
        _create_edge(repository, "C", "B")

        dependents = query_service.find_dependents("B")
        assert dependents == {"A", "C"}

    def test_find_dependents_none(self, repository, query_service):
        """Test finding dependents when none exist."""
        _create_edge(repository, "A", "B")

        dependents = query_service.find_dependents("A")
        assert dependents == set()

    def test_find_dependents_nonexistent(self, query_service):
        """Test finding dependents of non-existent artifact."""
        dependents = query_service.find_dependents("nonexistent")
        assert dependents == set()


class TestFindDependencies:
    """Tests for find_dependencies (what X depends on)."""

    def test_find_direct_dependencies(self, repository, query_service):
        """Test finding direct dependencies of an artifact."""
        _create_edge(repository, "A", "B")
        _create_edge(repository, "A", "C")

        dependencies = query_service.find_dependencies("A")
        assert dependencies == {"B", "C"}

    def test_find_dependencies_none(self, repository, query_service):
        """Test finding dependencies when none exist."""
        _create_edge(repository, "A", "B")

        dependencies = query_service.find_dependencies("B")
        assert dependencies == set()


class TestTransitiveDependents:
    """Tests for find_transitive_dependents."""

    def test_transitive_dependents(self, repository, query_service):
        """Test finding transitive dependents (upstream impact)."""
        # A -> B -> C
        _create_edge(repository, "A", "B")
        _create_edge(repository, "B", "C")

        # Who is transitively affected by C?
        transitive = query_service.find_transitive_dependents("C")
        assert transitive == {"A", "B"}

    def test_transitive_dependents_diamond(self, repository, query_service):
        """Test transitive dependents in diamond graph."""
        # A -> B -> D, A -> C -> D
        _create_edge(repository, "A", "B")
        _create_edge(repository, "A", "C")
        _create_edge(repository, "B", "D")
        _create_edge(repository, "C", "D")

        transitive = query_service.find_transitive_dependents("D")
        assert transitive == {"A", "B", "C"}

    def test_transitive_dependents_none(self, repository, query_service):
        """Test transitive dependents when none exist."""
        _create_edge(repository, "A", "B")

        transitive = query_service.find_transitive_dependents("A")
        assert transitive == set()


class TestTransitiveDependencies:
    """Tests for find_transitive_dependencies."""

    def test_transitive_dependencies(self, repository, query_service):
        """Test finding transitive dependencies (full dep tree)."""
        # A -> B -> C
        _create_edge(repository, "A", "B")
        _create_edge(repository, "B", "C")

        # What does A transitively depend on?
        transitive = query_service.find_transitive_dependencies("A")
        assert transitive == {"B", "C"}

    def test_transitive_dependencies_complex(self, repository, query_service):
        """Test transitive dependencies in complex graph."""
        # A -> B -> D, A -> C -> D, D -> E
        _create_edge(repository, "A", "B")
        _create_edge(repository, "A", "C")
        _create_edge(repository, "B", "D")
        _create_edge(repository, "C", "D")
        _create_edge(repository, "D", "E")

        transitive = query_service.find_transitive_dependencies("A")
        assert transitive == {"B", "C", "D", "E"}

    def test_transitive_dependencies_leaf(self, repository, query_service):
        """Test transitive dependencies of a leaf node."""
        _create_edge(repository, "A", "B")

        transitive = query_service.find_transitive_dependencies("B")
        assert transitive == set()


class TestDependencyChain:
    """Tests for get_dependency_chain (BFS path finding)."""

    def test_direct_chain(self, repository, query_service):
        """Test finding a direct dependency chain."""
        _create_edge(repository, "A", "B")

        chain = query_service.get_dependency_chain("A", "B")
        assert chain == ["A", "B"]

    def test_multi_hop_chain(self, repository, query_service):
        """Test finding a multi-hop dependency chain."""
        _create_edge(repository, "A", "B")
        _create_edge(repository, "B", "C")
        _create_edge(repository, "C", "D")

        chain = query_service.get_dependency_chain("A", "D")
        assert chain is not None
        assert chain[0] == "A"
        assert chain[-1] == "D"
        assert len(chain) == 4

    def test_no_chain_exists(self, repository, query_service):
        """Test when no chain exists between artifacts."""
        _create_edge(repository, "A", "B")
        _create_edge(repository, "C", "D")

        chain = query_service.get_dependency_chain("A", "D")
        assert chain is None

    def test_same_source_target(self, query_service):
        """Test chain from node to itself."""
        chain = query_service.get_dependency_chain("A", "A")
        assert chain == ["A"]

    def test_shortest_chain(self, repository, query_service):
        """Test BFS finds shortest chain."""
        # A -> B -> C (length 3)
        # A -> C (length 2, shorter)
        _create_edge(repository, "A", "B")
        _create_edge(repository, "B", "C")
        _create_edge(repository, "A", "C")

        chain = query_service.get_dependency_chain("A", "C")
        assert chain == ["A", "C"]


class TestCircularDependencyDetection:
    """Tests for detect_circular_dependencies."""

    def test_no_cycles(self, repository, query_service):
        """Test detection when no cycles exist."""
        _create_edge(repository, "A", "B")
        _create_edge(repository, "B", "C")

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

    def test_detect_simple_cycle(self, repository, query_service):
        """Test detecting a simple cycle."""
        _create_edge(repository, "A", "B")
        _create_edge(repository, "B", "A")

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

    def test_detect_three_node_cycle(self, repository, query_service):
        """Test detecting a 3-node cycle."""
        _create_edge(repository, "A", "B")
        _create_edge(repository, "B", "C")
        _create_edge(repository, "C", "A")

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


class TestWouldCreateCycle:
    """Tests for would_create_cycle pre-validation."""

    def test_would_not_create_cycle(self, repository, query_service):
        """Test edge that would not create a cycle."""
        _create_edge(repository, "A", "B")

        assert query_service.would_create_cycle("B", "C") is False

    def test_would_create_cycle_direct(self, repository, query_service):
        """Test edge that would create a direct cycle."""
        _create_edge(repository, "A", "B")

        assert query_service.would_create_cycle("B", "A") is True

    def test_would_create_cycle_transitive(self, repository, query_service):
        """Test edge that would create a transitive cycle."""
        _create_edge(repository, "A", "B")
        _create_edge(repository, "B", "C")

        assert query_service.would_create_cycle("C", "A") is True

    def test_self_reference_creates_cycle(self, query_service):
        """Test self-reference always creates a cycle."""
        assert query_service.would_create_cycle("A", "A") is True

    def test_safe_parallel_edge(self, repository, query_service):
        """Test parallel forward edge is safe."""
        _create_edge(repository, "A", "B")
        _create_edge(repository, "B", "C")

        # Adding A -> C doesn't create a cycle
        assert query_service.would_create_cycle("A", "C") is False


class TestBuildOrder:
    """Tests for get_build_order (topological sort)."""

    def test_simple_build_order(self, repository, query_service):
        """Test simple build order (dependencies before dependents)."""
        # A requires B, B requires C → build C first, then B, then A
        _create_edge(repository, "A", "B")
        _create_edge(repository, "B", "C")

        order = query_service.get_build_order()
        assert order.index("C") < order.index("B")
        assert order.index("B") < order.index("A")

    def test_build_order_diamond(self, repository, query_service):
        """Test build order with diamond dependencies."""
        # A requires B and C, B and C require D → build D first, then B/C, then A
        _create_edge(repository, "A", "B")
        _create_edge(repository, "A", "C")
        _create_edge(repository, "B", "D")
        _create_edge(repository, "C", "D")

        order = query_service.get_build_order()
        assert order.index("D") < order.index("B")
        assert order.index("D") < order.index("C")
        assert order.index("B") < order.index("A")
        assert order.index("C") < order.index("A")

    def test_build_order_scoped(self, repository, query_service):
        """Test build order with scoped artifact set."""
        _create_edge(repository, "A", "B")
        _create_edge(repository, "B", "C")
        _create_edge(repository, "C", "D")

        order = query_service.get_build_order(artifact_ids={"A", "B", "C"})
        assert len(order) == 3
        assert "D" not in order
        assert order.index("C") < order.index("B")
        assert order.index("B") < order.index("A")

    def test_build_order_with_cycle_raises_error(self, repository, query_service):
        """Test build order raises error on cycle."""
        _create_edge(repository, "A", "B")
        _create_edge(repository, "B", "C")
        _create_edge(repository, "C", "A")

        with pytest.raises(CircularDependencyError):
            query_service.get_build_order()

    def test_build_order_empty(self, query_service):
        """Test build order with no edges."""
        order = query_service.get_build_order()
        assert order == []
