"""
Unit tests for GraphBuilder.

Tests extracting edges from manifest, persisting them,
building graphs, and artifact-scoped subgraphs.
"""

import pytest
import tempfile
from pathlib import Path

from markitect.prompts.dependencies.models import (
    DependencyEdge,
    DependencyGraph,
    EdgeType,
)
from markitect.prompts.dependencies.repository import SQLiteDependencyRepository
from markitect.prompts.dependencies.graph import GraphBuilder
from markitect.prompts.execution.manifest import (
    DependencyEdge as ManifestDependencyEdge,
    RunManifest,
)


@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 builder(repository):
    """Create GraphBuilder instance."""
    return GraphBuilder(repository)


@pytest.fixture
def sample_manifest():
    """Create a sample RunManifest with dependency edges."""
    manifest = RunManifest.create(
        run_id="run-1",
        template_id="template-1",
        template_name="test-template",
        template_digest="abc123",
    )
    manifest.add_dependency_edge("artifact-1", "artifact-2", "requires")
    manifest.add_dependency_edge("artifact-2", "artifact-3", "generates")
    manifest.add_dependency_edge("artifact-1", "artifact-3", "includes")
    return manifest


class TestGraphBuilderExtract:
    """Tests for extracting edges from manifests."""

    def test_extract_edges_from_manifest(self, builder, sample_manifest):
        """Test extracting persistent edges from a manifest."""
        edges = builder.extract_edges(sample_manifest)

        assert len(edges) == 3
        assert all(isinstance(e, DependencyEdge) for e in edges)

    def test_extracted_edge_fields(self, builder, sample_manifest):
        """Test extracted edges have correct fields."""
        edges = builder.extract_edges(sample_manifest)

        # First edge: artifact-1 -> artifact-2, requires
        edge = edges[0]
        assert edge.source_artifact_id == "artifact-1"
        assert edge.target_artifact_id == "artifact-2"
        assert edge.run_id == "run-1"
        assert edge.edge_type == EdgeType.REQUIRES
        assert edge.id  # UUID assigned

    def test_extracted_edges_have_unique_ids(self, builder, sample_manifest):
        """Test each extracted edge gets a unique ID."""
        edges = builder.extract_edges(sample_manifest)
        ids = [e.id for e in edges]
        assert len(set(ids)) == len(ids)

    def test_extract_edges_empty_manifest(self, builder):
        """Test extracting from manifest with no edges."""
        manifest = RunManifest.create(
            run_id="run-empty",
            template_id="template-1",
            template_name="empty",
            template_digest="xyz",
        )
        edges = builder.extract_edges(manifest)
        assert edges == []

    def test_extract_edges_preserves_types(self, builder, sample_manifest):
        """Test all edge types are correctly mapped."""
        edges = builder.extract_edges(sample_manifest)
        types = [e.edge_type for e in edges]

        assert EdgeType.REQUIRES in types
        assert EdgeType.GENERATES in types
        assert EdgeType.INCLUDES in types


class TestGraphBuilderPersist:
    """Tests for persisting edges from manifests."""

    def test_persist_edges(self, builder, repository, sample_manifest):
        """Test persisting edges to repository."""
        persisted = builder.persist_edges(sample_manifest)

        assert len(persisted) == 3
        # Verify in repository
        all_edges = repository.get_all()
        assert len(all_edges) == 3

    def test_persisted_edges_retrievable(self, builder, repository, sample_manifest):
        """Test persisted edges can be retrieved from repository."""
        builder.persist_edges(sample_manifest)

        edges_from_source = repository.get_by_source("artifact-1")
        assert len(edges_from_source) == 2  # artifact-1 -> artifact-2 and artifact-3

        edges_from_run = repository.get_by_run("run-1")
        assert len(edges_from_run) == 3


class TestGraphBuilderBuildGraph:
    """Tests for building dependency graphs."""

    def test_build_graph_from_all_edges(self, builder, repository):
        """Test building graph from all stored edges."""
        # Manually create some edges
        for src, tgt in [("A", "B"), ("B", "C"), ("A", "C")]:
            edge = DependencyEdge.create(
                source_artifact_id=src,
                target_artifact_id=tgt,
                run_id="run-1",
                edge_type=EdgeType.REQUIRES,
            )
            repository.create(edge)

        graph = builder.build_graph()

        assert len(graph.nodes) == 3
        assert graph.has_edge("A", "B")
        assert graph.has_edge("B", "C")
        assert graph.has_edge("A", "C")

    def test_build_graph_scoped(self, builder, repository):
        """Test building scoped graph with subset of artifacts."""
        for src, tgt in [("A", "B"), ("B", "C"), ("C", "D")]:
            edge = DependencyEdge.create(
                source_artifact_id=src,
                target_artifact_id=tgt,
                run_id="run-1",
                edge_type=EdgeType.REQUIRES,
            )
            repository.create(edge)

        graph = builder.build_graph(artifact_ids={"A", "B", "C"})

        assert graph.has_edge("A", "B")
        assert graph.has_edge("B", "C")
        assert not graph.has_edge("C", "D")
        assert "D" not in graph.nodes

    def test_build_graph_scoped_includes_isolated_nodes(self, builder, repository):
        """Test scoped graph includes nodes with no edges in scope."""
        edge = DependencyEdge.create(
            source_artifact_id="A",
            target_artifact_id="B",
            run_id="run-1",
            edge_type=EdgeType.REQUIRES,
        )
        repository.create(edge)

        graph = builder.build_graph(artifact_ids={"A", "B", "C"})

        assert "C" in graph.nodes

    def test_build_graph_empty(self, builder):
        """Test building graph from empty repository."""
        graph = builder.build_graph()
        assert len(graph.nodes) == 0
        assert graph.edge_count == 0

    def test_build_graph_for_run(self, builder, repository):
        """Test building graph from a specific run's edges."""
        # Run 1 edges
        for src, tgt in [("A", "B"), ("B", "C")]:
            edge = DependencyEdge.create(
                source_artifact_id=src,
                target_artifact_id=tgt,
                run_id="run-1",
                edge_type=EdgeType.REQUIRES,
            )
            repository.create(edge)

        # Run 2 edges
        edge = DependencyEdge.create(
            source_artifact_id="X",
            target_artifact_id="Y",
            run_id="run-2",
            edge_type=EdgeType.GENERATES,
        )
        repository.create(edge)

        graph = builder.build_graph_for_run("run-1")

        assert graph.has_edge("A", "B")
        assert graph.has_edge("B", "C")
        assert not graph.has_edge("X", "Y")

    def test_build_graph_preserves_edge_types(self, builder, repository):
        """Test built graph preserves edge types."""
        edge = DependencyEdge.create(
            source_artifact_id="A",
            target_artifact_id="B",
            run_id="run-1",
            edge_type=EdgeType.GENERATES,
        )
        repository.create(edge)

        graph = builder.build_graph()
        assert graph.get_edge_type("A", "B") == EdgeType.GENERATES
