"""
Unit tests for dependency repository.

Tests CRUD operations, duplicate handling, and query by
source, target, run, and type.
"""

import pytest
import tempfile
from pathlib import Path

from markitect.prompts.dependencies.models import DependencyEdge, EdgeType
from markitect.prompts.dependencies.repository import (
    SQLiteDependencyRepository,
    DuplicateDependencyError,
)


@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 sample_edge():
    """Create sample dependency edge for testing."""
    return DependencyEdge.create(
        source_artifact_id="artifact-1",
        target_artifact_id="artifact-2",
        run_id="run-1",
        edge_type=EdgeType.REQUIRES,
    )


class TestSQLiteDependencyRepository:
    """Tests for SQLiteDependencyRepository."""

    def test_create_edge(self, repository, sample_edge):
        """Test creating a dependency edge."""
        created = repository.create(sample_edge)
        assert created.id == sample_edge.id
        assert created.source_artifact_id == "artifact-1"
        assert created.target_artifact_id == "artifact-2"

    def test_create_duplicate_edge_raises_error(self, repository):
        """Test creating duplicate edge raises error."""
        edge1 = DependencyEdge.create(
            source_artifact_id="a",
            target_artifact_id="b",
            run_id="r1",
            edge_type=EdgeType.REQUIRES,
        )
        repository.create(edge1)

        edge2 = DependencyEdge.create(
            source_artifact_id="a",
            target_artifact_id="b",
            run_id="r1",
            edge_type=EdgeType.REQUIRES,
        )

        with pytest.raises(DuplicateDependencyError, match="already exists"):
            repository.create(edge2)

    def test_same_edge_different_runs_allowed(self, repository):
        """Test same source/target with different runs is allowed."""
        edge1 = DependencyEdge.create(
            source_artifact_id="a",
            target_artifact_id="b",
            run_id="run-1",
            edge_type=EdgeType.REQUIRES,
        )
        edge2 = DependencyEdge.create(
            source_artifact_id="a",
            target_artifact_id="b",
            run_id="run-2",
            edge_type=EdgeType.REQUIRES,
        )
        repository.create(edge1)
        repository.create(edge2)  # Should not raise

    def test_get_by_id(self, repository, sample_edge):
        """Test retrieving edge by ID."""
        repository.create(sample_edge)
        retrieved = repository.get_by_id(sample_edge.id)

        assert retrieved is not None
        assert retrieved.id == sample_edge.id
        assert retrieved.source_artifact_id == sample_edge.source_artifact_id
        assert retrieved.edge_type == sample_edge.edge_type

    def test_get_by_id_not_found(self, repository):
        """Test retrieving non-existent edge returns None."""
        assert repository.get_by_id("nonexistent") is None

    def test_get_by_source(self, repository):
        """Test querying edges by source artifact."""
        edge1 = DependencyEdge.create(
            source_artifact_id="src-1",
            target_artifact_id="tgt-1",
            run_id="run-1",
            edge_type=EdgeType.REQUIRES,
        )
        edge2 = DependencyEdge.create(
            source_artifact_id="src-1",
            target_artifact_id="tgt-2",
            run_id="run-1",
            edge_type=EdgeType.GENERATES,
        )
        edge3 = DependencyEdge.create(
            source_artifact_id="src-2",
            target_artifact_id="tgt-3",
            run_id="run-1",
            edge_type=EdgeType.REQUIRES,
        )
        repository.create(edge1)
        repository.create(edge2)
        repository.create(edge3)

        edges = repository.get_by_source("src-1")
        assert len(edges) == 2
        assert all(e.source_artifact_id == "src-1" for e in edges)

    def test_get_by_target(self, repository):
        """Test querying edges by target artifact."""
        edge1 = DependencyEdge.create(
            source_artifact_id="src-1",
            target_artifact_id="tgt-1",
            run_id="run-1",
            edge_type=EdgeType.REQUIRES,
        )
        edge2 = DependencyEdge.create(
            source_artifact_id="src-2",
            target_artifact_id="tgt-1",
            run_id="run-1",
            edge_type=EdgeType.REQUIRES,
        )
        repository.create(edge1)
        repository.create(edge2)

        edges = repository.get_by_target("tgt-1")
        assert len(edges) == 2
        assert all(e.target_artifact_id == "tgt-1" for e in edges)

    def test_get_by_run(self, repository):
        """Test querying edges by run ID."""
        edge1 = DependencyEdge.create(
            source_artifact_id="a",
            target_artifact_id="b",
            run_id="run-1",
            edge_type=EdgeType.REQUIRES,
        )
        edge2 = DependencyEdge.create(
            source_artifact_id="c",
            target_artifact_id="d",
            run_id="run-1",
            edge_type=EdgeType.GENERATES,
        )
        edge3 = DependencyEdge.create(
            source_artifact_id="e",
            target_artifact_id="f",
            run_id="run-2",
            edge_type=EdgeType.REQUIRES,
        )
        repository.create(edge1)
        repository.create(edge2)
        repository.create(edge3)

        edges = repository.get_by_run("run-1")
        assert len(edges) == 2
        assert all(e.run_id == "run-1" for e in edges)

    def test_get_by_type(self, repository):
        """Test querying edges by type."""
        edge1 = DependencyEdge.create(
            source_artifact_id="a",
            target_artifact_id="b",
            run_id="run-1",
            edge_type=EdgeType.REQUIRES,
        )
        edge2 = DependencyEdge.create(
            source_artifact_id="c",
            target_artifact_id="d",
            run_id="run-1",
            edge_type=EdgeType.GENERATES,
        )
        edge3 = DependencyEdge.create(
            source_artifact_id="e",
            target_artifact_id="f",
            run_id="run-1",
            edge_type=EdgeType.REQUIRES,
        )
        repository.create(edge1)
        repository.create(edge2)
        repository.create(edge3)

        requires_edges = repository.get_by_type(EdgeType.REQUIRES)
        assert len(requires_edges) == 2
        assert all(e.edge_type == EdgeType.REQUIRES for e in requires_edges)

        generates_edges = repository.get_by_type(EdgeType.GENERATES)
        assert len(generates_edges) == 1

    def test_get_all(self, repository):
        """Test getting all edges."""
        for i in range(3):
            edge = DependencyEdge.create(
                source_artifact_id=f"src-{i}",
                target_artifact_id=f"tgt-{i}",
                run_id="run-1",
                edge_type=EdgeType.REQUIRES,
            )
            repository.create(edge)

        all_edges = repository.get_all()
        assert len(all_edges) == 3

    def test_get_all_empty(self, repository):
        """Test getting all edges from empty repository."""
        assert repository.get_all() == []

    def test_delete_edge(self, repository, sample_edge):
        """Test deleting an edge."""
        repository.create(sample_edge)
        assert repository.delete(sample_edge.id) is True
        assert repository.get_by_id(sample_edge.id) is None

    def test_delete_nonexistent_edge(self, repository):
        """Test deleting non-existent edge returns False."""
        assert repository.delete("nonexistent") is False

    def test_delete_by_run(self, repository):
        """Test deleting all edges for a run."""
        for i in range(3):
            edge = DependencyEdge.create(
                source_artifact_id=f"src-{i}",
                target_artifact_id=f"tgt-{i}",
                run_id="run-1",
                edge_type=EdgeType.REQUIRES,
            )
            repository.create(edge)

        other_edge = DependencyEdge.create(
            source_artifact_id="other-src",
            target_artifact_id="other-tgt",
            run_id="run-2",
            edge_type=EdgeType.REQUIRES,
        )
        repository.create(other_edge)

        deleted = repository.delete_by_run("run-1")
        assert deleted == 3
        assert len(repository.get_by_run("run-1")) == 0
        assert len(repository.get_by_run("run-2")) == 1

    def test_delete_by_run_no_matches(self, repository):
        """Test deleting by run with no matches returns 0."""
        assert repository.delete_by_run("nonexistent") == 0

    def test_edge_type_preserved(self, repository):
        """Test that edge type is correctly stored and retrieved."""
        for edge_type in EdgeType:
            edge = DependencyEdge.create(
                source_artifact_id=f"src-{edge_type.value}",
                target_artifact_id=f"tgt-{edge_type.value}",
                run_id="run-1",
                edge_type=edge_type,
            )
            repository.create(edge)
            retrieved = repository.get_by_id(edge.id)
            assert retrieved.edge_type == edge_type
