"""
Integration test for full incremental recompute workflow.

Tests: change artifact → detect → find dependents → recompute
with a real SQLite database.
"""

import pytest
import tempfile
from pathlib import Path

from markitect.prompts.models import Artifact, ArtifactType, calculate_content_digest
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


@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 (shares same DB)."""
    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):
    """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 TestFullRecomputeWorkflow:
    """Full end-to-end incremental recompute workflow."""

    def test_change_detect_and_recompute(
        self, artifact_repo, dep_repo, query_service, detector, engine
    ):
        """Test complete flow: create artifacts, detect change, recompute dependents."""
        # Step 1: Create artifacts
        lib = _create_artifact(artifact_repo, "space-1", "lib", "library v1")
        app = _create_artifact(artifact_repo, "space-1", "app", "app using lib")

        # Step 2: Establish dependency (app depends on lib)
        _create_edge(dep_repo, app.id, lib.id)

        # Step 3: Detect a change in lib
        change = detector.detect_change(lib, "library v2")
        assert change is not None
        detector.record_change(change)

        # Step 4: Recompute dependents
        executed_ids = []

        def callback(dep_id):
            from markitect.prompts.execution.models import PromptRun
            run = PromptRun.create(
                template_id=dep_id,
                input_bundle_hash="recompute-hash",
            )
            run.mark_complete()
            executed_ids.append(dep_id)
            return run

        result = engine.recompute(
            change,
            config=RecomputeConfig(max_depth=1),
            execution_callback=callback,
            old_content="library v1",
            new_content="library v2",
        )

        # Verify
        assert result.total_dependents == 1
        assert result.recomputed_count == 1
        assert result.suppressed_count == 0
        assert app.id in executed_ids

    def test_multi_level_recompute(
        self, artifact_repo, dep_repo, query_service, detector, engine
    ):
        """Test recompute propagates through multiple dependency levels."""
        # core -> utils -> app
        core = _create_artifact(artifact_repo, "space-1", "core", "core v1")
        utils = _create_artifact(artifact_repo, "space-1", "utils", "utils v1")
        app = _create_artifact(artifact_repo, "space-1", "app", "app v1")

        _create_edge(dep_repo, utils.id, core.id)
        _create_edge(dep_repo, app.id, utils.id)

        # Change core
        change = detector.detect_change(core, "core v2")
        assert change is not None
        detector.record_change(change)

        # Recompute with depth 2
        result = engine.recompute(
            change,
            config=RecomputeConfig(max_depth=2),
            old_content="core v1",
            new_content="core v2",
        )

        assert result.total_dependents == 2
        assert result.recomputed_count == 2
        assert set(result.executed_run_ids) == {utils.id, app.id}

    def test_no_change_no_recompute(
        self, artifact_repo, dep_repo, detector, engine
    ):
        """Test that no change means no recompute."""
        lib = _create_artifact(artifact_repo, "space-1", "lib", "unchanged")
        app = _create_artifact(artifact_repo, "space-1", "app", "app")

        _create_edge(dep_repo, app.id, lib.id)

        # Same content → no change
        change = detector.detect_change(lib, "unchanged")
        assert change is None

    def test_change_record_persisted(
        self, artifact_repo, detector
    ):
        """Test change records are persisted across detector instances."""
        lib = _create_artifact(artifact_repo, "space-1", "lib", "v1")

        change = detector.detect_change(lib, "v2")
        assert change is not None
        detector.record_change(change)

        # Verify persisted
        changes = detector.get_changes_for_artifact(lib.id)
        assert len(changes) == 1
        assert changes[0].id == change.id


class TestMultipleArtifactChanges:
    """Tests for handling changes to multiple artifacts."""

    def test_independent_changes(
        self, artifact_repo, dep_repo, detector, engine
    ):
        """Test independent artifact changes trigger separate recomputes."""
        lib_a = _create_artifact(artifact_repo, "space-1", "lib-a", "lib-a v1")
        lib_b = _create_artifact(artifact_repo, "space-1", "lib-b", "lib-b v1")
        app = _create_artifact(artifact_repo, "space-1", "app", "app v1")

        _create_edge(dep_repo, app.id, lib_a.id)
        _create_edge(dep_repo, app.id, lib_b.id)

        # Change lib_a
        change_a = detector.detect_change(lib_a, "lib-a v2")
        assert change_a is not None
        detector.record_change(change_a)

        result_a = engine.recompute(
            change_a,
            config=RecomputeConfig(max_depth=1),
            old_content="lib-a v1",
            new_content="lib-a v2",
        )

        assert result_a.total_dependents == 1
        assert result_a.recomputed_count == 1

        # Change lib_b
        change_b = detector.detect_change(lib_b, "lib-b v2")
        assert change_b is not None
        detector.record_change(change_b)

        result_b = engine.recompute(
            change_b,
            config=RecomputeConfig(max_depth=1),
            old_content="lib-b v1",
            new_content="lib-b v2",
        )

        assert result_b.total_dependents == 1
        assert result_b.recomputed_count == 1
