"""
Integration tests for circular dependency suppression.

Tests circular dependency handling with real DB, debt recording,
and various cycle topologies.
"""

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
from markitect.prompts.dependencies.repository import SQLiteDependencyRepository
from markitect.prompts.dependencies.queries import DependencyQueryService
from markitect.prompts.incremental.detector import ChangeDetector
from markitect.prompts.incremental.engine import IncrementalExecutionEngine
from markitect.prompts.incremental.models import RecomputeConfig, ChangeType


@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 query_service(dep_repo):
    """Create DependencyQueryService."""
    return DependencyQueryService(dep_repo)


@pytest.fixture
def detector(temp_db):
    """Create ChangeDetector."""
    return ChangeDetector(temp_db)


@pytest.fixture
def engine(temp_db, query_service):
    """Create IncrementalExecutionEngine."""
    return IncrementalExecutionEngine(temp_db, query_service)


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


def _create_edge(repo, src, tgt, run_id="run-1"):
    """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=EdgeType.REQUIRES,
    )
    return repo.create(edge)


class TestDirectCircularSuppression:
    """Tests for direct circular dependency (A <-> B) suppression."""

    def test_mutual_dependency_suppressed(
        self, artifact_repo, dep_repo, detector, engine
    ):
        """Test mutual dependency suppresses recompute and records debt."""
        art_a = _create_artifact(artifact_repo, "space-1", "a", "content-a")
        art_b = _create_artifact(artifact_repo, "space-1", "b", "content-b")

        # A -> B and B -> A (circular)
        _create_edge(dep_repo, art_a.id, art_b.id)
        _create_edge(dep_repo, art_b.id, art_a.id)

        # Detect change in B
        change = detector.detect_change(art_b, "content-b-modified")
        assert change is not None
        detector.record_change(change)

        # Recompute: A depends on B, but A -> B creates cycle
        result = engine.recompute(
            change,
            config=RecomputeConfig(max_depth=1, suppress_circular=True),
            old_content="content-b",
            new_content="content-b-modified",
        )

        assert result.total_dependents == 1
        assert result.suppressed_count == 1
        assert result.recomputed_count == 0
        assert result.suppressed[0].suppression_reason == "circular_dependency"

    def test_debt_persisted_for_circular(
        self, artifact_repo, dep_repo, detector, engine
    ):
        """Test that circular suppression debt is persisted in DB."""
        art_a = _create_artifact(artifact_repo, "space-1", "a", "a-v1")
        art_b = _create_artifact(artifact_repo, "space-1", "b", "b-v1")

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

        change = detector.detect_change(art_b, "b-v2")
        detector.record_change(change)

        engine.recompute(
            change,
            config=RecomputeConfig(max_depth=1, suppress_circular=True),
            old_content="b-v1",
            new_content="b-v2",
        )

        # Verify debt persisted
        debt = engine.get_debt_for_artifact(art_b.id)
        assert len(debt) == 1
        assert debt[0].suppression_reason == "circular_dependency"
        assert debt[0].dependent_run_id == art_a.id


class TestThreeNodeCycleSuppression:
    """Tests for three-node circular dependency suppression."""

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

        # A -> B -> C -> A
        _create_edge(dep_repo, art_a.id, art_b.id)
        _create_edge(dep_repo, art_b.id, art_c.id)
        _create_edge(dep_repo, art_c.id, art_a.id)

        # Change C, dependent at depth 1 is B
        change = detector.detect_change(art_c, "c-modified")
        detector.record_change(change)

        result = engine.recompute(
            change,
            config=RecomputeConfig(max_depth=1, suppress_circular=True),
            old_content="c",
            new_content="c-modified",
        )

        # B depends on C. would_create_cycle(B, C) checks if C can reach B.
        # C -> A -> B: yes, C can reach B. So B is suppressed.
        assert result.total_dependents == 1
        assert result.suppressed_count == 1
        assert result.suppressed[0].suppression_reason == "circular_dependency"


class TestMixedCircularAndNormal:
    """Tests with a mix of circular and normal dependencies."""

    def test_some_suppressed_some_recomputed(
        self, artifact_repo, dep_repo, detector, engine
    ):
        """Test graph with both circular and normal dependents."""
        art_a = _create_artifact(artifact_repo, "space-1", "a", "a")
        art_b = _create_artifact(artifact_repo, "space-1", "b", "b")
        art_c = _create_artifact(artifact_repo, "space-1", "c", "c")

        # B -> A (normal), A -> B (creates cycle with B -> A)
        # C -> A (normal, no cycle)
        _create_edge(dep_repo, art_b.id, art_a.id)
        _create_edge(dep_repo, art_a.id, art_b.id)
        _create_edge(dep_repo, art_c.id, art_a.id)

        # Change A: dependents are B and C
        change = detector.detect_change(art_a, "a-modified")
        detector.record_change(change)

        result = engine.recompute(
            change,
            config=RecomputeConfig(max_depth=1, suppress_circular=True),
            old_content="a",
            new_content="a-modified",
        )

        assert result.total_dependents == 2

        # B has circular dep with A → suppressed
        # C has no circular dep with A → recomputed
        circular_debts = [d for d in result.suppressed if d.suppression_reason == "circular_dependency"]
        assert len(circular_debts) == 1
        assert circular_debts[0].dependent_run_id == art_b.id

        assert result.recomputed_count == 1
        assert art_c.id in result.executed_run_ids
