"""Tests for multi-file / filesystem-as-tree support.

Tests that ProjectStore, directory parsing, multi-file navigation,
multi-file mutations, and project tools work correctly.
Also verifies backward compatibility with single-file TreeStore.
"""

from __future__ import annotations

from pathlib import Path

import pytest

from carve.models import ProjectStore, TreeStore  # noqa: I001
from carve.parser import TreeSitterParser
from carve.server import CarveServer
from carve.tools import (
    ancestors,
    create_file,
    delete,
    insert,
    inspect,
    project_import,
    project_serialize,
    search,
    subtree,
    update,
)

# =============================================================================
# Fixtures
# =============================================================================


@pytest.fixture
def parser() -> TreeSitterParser:
    return TreeSitterParser()


@pytest.fixture
def sample_project(tmp_path: Path) -> Path:
    """Create a small multi-file project on disk."""
    project = tmp_path / "myproject"
    project.mkdir()

    # app/models.py
    app = project / "app"
    app.mkdir()
    (app / "models.py").write_text(
        "class User:\n    def __init__(self, name: str):\n        self.name = name\n\n"
        "class Order:\n    def __init__(self, user: User, total: float):\n"
        "        self.user = user\n        self.total = total\n"
    )

    # app/views.py
    (app / "views.py").write_text(
        'def index():\n    return "Hello"\n\n'
        'def user_detail(user_id: int):\n    return f"User {user_id}"\n'
    )

    # config.py
    (project / "config.py").write_text('DATABASE_URL = "sqlite:///db.sqlite"\nDEBUG = True\n')

    return project


@pytest.fixture
def project_store(parser: TreeSitterParser, sample_project: Path) -> ProjectStore:
    return parser.to_project_store(sample_project)


# =============================================================================
# TestProjectStore - Model tests
# =============================================================================


class TestProjectStore:
    def test_create_empty(self) -> None:
        store = ProjectStore()
        assert store.root is None
        assert store.file_sources == {}
        assert store.file_languages == {}

    def test_get_node_empty(self) -> None:
        store = ProjectStore()
        assert store.get_node("anything") is None

    def test_all_nodes_empty(self) -> None:
        store = ProjectStore()
        assert store.all_nodes() == []

    def test_get_file_for_code_node(self, project_store: ProjectStore) -> None:
        # A code node inside a file should resolve to that file
        file_id = project_store.get_file_for_node("myproject/app/models.py::User")
        assert file_id == "myproject/app/models.py"

    def test_get_file_for_file_node(self, project_store: ProjectStore) -> None:
        file_id = project_store.get_file_for_node("myproject/app/models.py")
        assert file_id == "myproject/app/models.py"

    def test_get_file_for_dir_node(self, project_store: ProjectStore) -> None:
        # Directory nodes don't have file sources
        file_id = project_store.get_file_for_node("myproject/app")
        assert file_id is None

    def test_get_source_for_code_node(self, project_store: ProjectStore) -> None:
        source = project_store.get_source_for_node("myproject/app/models.py::User")
        assert source is not None
        assert "class User" in source

    def test_get_language_for_code_node(self, project_store: ProjectStore) -> None:
        lang = project_store.get_language_for_node("myproject/app/models.py::User")
        assert lang == "python"

    def test_find_by_type(self, project_store: ProjectStore) -> None:
        files = project_store.find_by_type("file")
        assert len(files) == 3  # models.py, views.py, config.py

    def test_find_by_type_directory(self, project_store: ProjectStore) -> None:
        dirs = project_store.find_by_type("directory")
        assert len(dirs) >= 2  # myproject, app


# =============================================================================
# TestDirectoryParsing
# =============================================================================


class TestDirectoryParsing:
    def test_parse_simple_directory(self, parser: TreeSitterParser, sample_project: Path) -> None:
        store = parser.to_project_store(sample_project)
        assert store.root is not None
        assert store.root.node_type == "directory"
        assert store.root.node_id == "myproject"

    def test_file_count(self, project_store: ProjectStore) -> None:
        assert len(project_store.file_sources) == 3

    def test_file_languages(self, project_store: ProjectStore) -> None:
        for lang in project_store.file_languages.values():
            assert lang == "python"

    def test_nested_directory_structure(self, project_store: ProjectStore) -> None:
        root = project_store.root
        assert root is not None

        # Root should have app/ dir and config.py file as children
        child_ids = [c.node_id for c in root.children]
        assert "myproject/app" in child_ids
        assert "myproject/config.py" in child_ids

    def test_carveignore_excludes_pycache(
        self, parser: TreeSitterParser, sample_project: Path
    ) -> None:
        pycache = sample_project / "__pycache__"
        pycache.mkdir()
        (pycache / "models.cpython-312.pyc").write_bytes(b"fake")
        (sample_project / ".carveignore").write_text("__pycache__/\n")

        store = parser.to_project_store(sample_project)
        all_ids = [n.node_id for n in store.all_nodes()]
        assert not any("__pycache__" in nid for nid in all_ids)

    def test_carveignore_excludes_git_dir(
        self, parser: TreeSitterParser, sample_project: Path
    ) -> None:
        git_dir = sample_project / ".git"
        git_dir.mkdir()
        (git_dir / "config").write_text("fake")
        (sample_project / ".carveignore").write_text(".git/\n")

        store = parser.to_project_store(sample_project)
        all_ids = [n.node_id for n in store.all_nodes()]
        assert not any(".git" in nid for nid in all_ids)

    def test_reads_unsupported_files_as_text(
        self, parser: TreeSitterParser, sample_project: Path
    ) -> None:
        (sample_project / "README.md").write_text("# My Project")
        (sample_project / "Makefile").write_text("all: build")

        store = parser.to_project_store(sample_project)
        all_ids = [n.node_id for n in store.all_nodes()]
        assert any("README" in nid for nid in all_ids)
        assert any("Makefile" in nid for nid in all_ids)

    def test_empty_directory_excluded(self, parser: TreeSitterParser, tmp_path: Path) -> None:
        project = tmp_path / "emptyproj"
        project.mkdir()
        (project / "empty_subdir").mkdir()
        (project / "main.py").write_text("x = 1\n")

        store = parser.to_project_store(project)
        all_ids = [n.node_id for n in store.all_nodes()]
        assert not any("empty_subdir" in nid for nid in all_ids)

    def test_mixed_languages(self, parser: TreeSitterParser, tmp_path: Path) -> None:
        project = tmp_path / "mixedproj"
        project.mkdir()
        (project / "app.py").write_text("def hello():\n    pass\n")
        (project / "script.js").write_text("function greet() { return 'hi'; }\n")

        store = parser.to_project_store(project)
        assert store.file_languages["mixedproj/app.py"] == "python"
        assert store.file_languages["mixedproj/script.js"] == "javascript"

    def test_directory_node_types(self, project_store: ProjectStore) -> None:
        root = project_store.root
        assert root is not None
        assert root.node_type == "directory"

        app_node = project_store.get_node("myproject/app")
        assert app_node is not None
        assert app_node.node_type == "directory"

    def test_file_node_types(self, project_store: ProjectStore) -> None:
        models = project_store.get_node("myproject/app/models.py")
        assert models is not None
        assert models.node_type == "file"


# =============================================================================
# TestCarveignore
# =============================================================================


class TestCarveignore:
    def test_ignore_file_by_name(self, parser: TreeSitterParser, tmp_path: Path) -> None:
        project = tmp_path / "proj"
        project.mkdir()
        (project / "keep.py").write_text("x = 1\n")
        (project / "secret.py").write_text("y = 2\n")
        (project / ".carveignore").write_text("secret.py\n")

        store = parser.to_project_store(project)
        all_ids = [n.node_id for n in store.all_nodes()]
        assert any("keep.py" in nid for nid in all_ids)
        assert not any("secret.py" in nid for nid in all_ids)

    def test_ignore_directory(self, parser: TreeSitterParser, tmp_path: Path) -> None:
        project = tmp_path / "proj"
        project.mkdir()
        (project / "main.py").write_text("x = 1\n")
        vendor = project / "vendor"
        vendor.mkdir()
        (vendor / "lib.py").write_text("y = 2\n")
        (project / ".carveignore").write_text("vendor/\n")

        store = parser.to_project_store(project)
        all_ids = [n.node_id for n in store.all_nodes()]
        assert any("main.py" in nid for nid in all_ids)
        assert not any("vendor" in nid for nid in all_ids)

    def test_ignore_glob_pattern(self, parser: TreeSitterParser, tmp_path: Path) -> None:
        project = tmp_path / "proj"
        project.mkdir()
        (project / "app.py").write_text("x = 1\n")
        (project / "test_app.py").write_text("y = 2\n")
        (project / "test_utils.py").write_text("z = 3\n")
        (project / ".carveignore").write_text("test_*.py\n")

        store = parser.to_project_store(project)
        all_ids = [n.node_id for n in store.all_nodes()]
        assert any("app.py" in nid for nid in all_ids)
        assert not any("test_app.py" in nid for nid in all_ids)
        assert not any("test_utils.py" in nid for nid in all_ids)

    def test_ignore_nested_path(self, parser: TreeSitterParser, tmp_path: Path) -> None:
        project = tmp_path / "proj"
        project.mkdir()
        sub = project / "sub"
        sub.mkdir()
        (sub / "keep.py").write_text("x = 1\n")
        (sub / "skip.py").write_text("y = 2\n")
        (project / ".carveignore").write_text("sub/skip.py\n")

        store = parser.to_project_store(project)
        all_ids = [n.node_id for n in store.all_nodes()]
        assert any("keep.py" in nid for nid in all_ids)
        assert not any("skip.py" in nid for nid in all_ids)

    def test_no_carveignore_file(self, parser: TreeSitterParser, tmp_path: Path) -> None:
        """Without .carveignore, all supported files are included."""
        project = tmp_path / "proj"
        project.mkdir()
        (project / "a.py").write_text("x = 1\n")
        (project / "b.py").write_text("y = 2\n")

        store = parser.to_project_store(project)
        all_ids = [n.node_id for n in store.all_nodes()]
        assert any("a.py" in nid for nid in all_ids)
        assert any("b.py" in nid for nid in all_ids)

    def test_comments_and_blank_lines(self, parser: TreeSitterParser, tmp_path: Path) -> None:
        """Comments and blank lines in .carveignore are handled correctly."""
        project = tmp_path / "proj"
        project.mkdir()
        (project / "keep.py").write_text("x = 1\n")
        (project / "skip.py").write_text("y = 2\n")
        (project / ".carveignore").write_text("# a comment\n\nskip.py\n")

        store = parser.to_project_store(project)
        all_ids = [n.node_id for n in store.all_nodes()]
        assert any("keep.py" in nid for nid in all_ids)
        assert not any("skip.py" in nid for nid in all_ids)


# =============================================================================
# TestMultiFileNodeIds
# =============================================================================


class TestMultiFileNodeIds:
    def test_file_node_id_is_relative_path(self, project_store: ProjectStore) -> None:
        node = project_store.get_node("myproject/app/models.py")
        assert node is not None

    def test_directory_node_id(self, project_store: ProjectStore) -> None:
        node = project_store.get_node("myproject/app")
        assert node is not None

    def test_code_node_id_includes_file_path(self, project_store: ProjectStore) -> None:
        user = project_store.get_node("myproject/app/models.py::User")
        assert user is not None
        assert user.node_type == "class_definition"

    def test_nested_code_node_id(self, project_store: ProjectStore) -> None:
        init = project_store.get_node("myproject/app/models.py::User::__init__")
        assert init is not None
        assert init.node_type == "function_definition"

    def test_root_level_file_code_nodes(self, project_store: ProjectStore) -> None:
        # config.py is at root level
        node = project_store.get_node("myproject/config.py")
        assert node is not None
        assert node.node_type == "file"


# =============================================================================
# TestMultiFileNavigation
# =============================================================================


class TestMultiFileNavigation:
    def test_subtree_from_project_root(self, project_store: ProjectStore) -> None:
        result = subtree(project_store, "myproject", depth=1)
        assert "error" not in result
        assert result["node_type"] == "directory"
        assert "children" in result
        child_ids = [c["node_id"] for c in result["children"]]
        assert "myproject/app" in child_ids
        assert "myproject/config.py" in child_ids

    def test_subtree_from_directory(self, project_store: ProjectStore) -> None:
        result = subtree(project_store, "myproject/app", depth=1)
        assert "error" not in result
        child_ids = [c["node_id"] for c in result["children"]]
        assert "myproject/app/models.py" in child_ids
        assert "myproject/app/views.py" in child_ids

    def test_subtree_from_file(self, project_store: ProjectStore) -> None:
        result = subtree(project_store, "myproject/app/models.py", depth=1)
        assert "error" not in result
        assert result["node_type"] == "file"
        child_ids = [c["node_id"] for c in result["children"]]
        assert "myproject/app/models.py::User" in child_ids
        assert "myproject/app/models.py::Order" in child_ids

    def test_subtree_from_code_node(self, project_store: ProjectStore) -> None:
        result = subtree(project_store, "myproject/app/models.py::User", depth=1)
        assert "error" not in result
        assert result["node_type"] == "class_definition"

    def test_ancestors_from_code_to_root(self, project_store: ProjectStore) -> None:
        result = ancestors(project_store, "myproject/app/models.py::User::__init__")
        assert len(result) >= 4  # __init__ -> User -> models.py -> app -> myproject
        assert result[0]["node_id"] == "myproject/app/models.py::User::__init__"
        # Last should be root
        assert result[-1]["node_id"] == "myproject"

    def test_inspect_code_node(self, project_store: ProjectStore) -> None:
        result = inspect(project_store, "myproject/app/models.py::User")
        assert "error" not in result
        assert result["type"] == "class_definition"
        assert "class User" in result["source"]

    def test_inspect_file_node(self, project_store: ProjectStore) -> None:
        result = inspect(project_store, "myproject/app/views.py")
        assert "error" not in result
        assert result["type"] == "file"
        assert "def index" in result["source"]

    def test_inspect_directory_node(self, project_store: ProjectStore) -> None:
        result = inspect(project_store, "myproject/app")
        assert "error" not in result
        assert result["type"] == "directory"

    def test_search_across_files(self, project_store: ProjectStore) -> None:
        # Search for "User" should find it in models.py
        results = search(project_store, pattern="User")
        ids = [r["node_id"] for r in results]
        assert any("models.py::User" in nid for nid in ids)

    def test_search_by_type_file(self, project_store: ProjectStore) -> None:
        results = search(project_store, node_type="file")
        assert len(results) == 3

    def test_search_by_type_directory(self, project_store: ProjectStore) -> None:
        results = search(project_store, node_type="directory")
        assert len(results) >= 2  # myproject, app

    def test_search_scoped_to_directory(self, project_store: ProjectStore) -> None:
        results = search(project_store, pattern="index", scope="myproject/app/views.py")
        assert len(results) >= 1
        assert all("views.py" in r["node_id"] for r in results)

    def test_search_scoped_to_file(self, project_store: ProjectStore) -> None:
        results = search(
            project_store,
            node_type="function_definition",
            scope="myproject/app/views.py",
        )
        ids = [r["node_id"] for r in results]
        assert any("index" in nid for nid in ids)
        assert any("user_detail" in nid for nid in ids)


# =============================================================================
# TestMultiFileMutations
# =============================================================================


class TestMultiFileMutations:
    def test_update_code_in_multifile(self, project_store: ProjectStore) -> None:
        new_snippet = 'def index():\n    return "Updated Hello"\n'
        result = update(
            project_store,
            "myproject/app/views.py::index",
            new_snippet,
        )
        assert result.get("success") is True

        # Verify the source was updated
        node = project_store.get_node("myproject/app/views.py::index")
        assert node is not None
        assert "Updated Hello" in node.source

    def test_mutation_only_affects_target_file(self, project_store: ProjectStore) -> None:
        # Get models.py source before mutation
        models_source_before = project_store.file_sources["myproject/app/models.py"]

        # Mutate views.py
        update(
            project_store,
            "myproject/app/views.py::index",
            'def index():\n    return "Changed"\n',
        )

        # models.py should be unchanged
        assert project_store.file_sources["myproject/app/models.py"] == models_source_before

    def test_insert_into_multifile(self, project_store: ProjectStore) -> None:
        snippet = "def new_view():\n    return 'new'\n"
        result = insert(
            project_store,
            "myproject/app/views.py",
            "end",
            snippet,
        )
        assert "error" not in result
        assert "views.py::new_view" in result["node_id"]

    def test_delete_from_multifile(self, project_store: ProjectStore) -> None:
        result = delete(project_store, "myproject/app/views.py::user_detail")
        assert result.get("success") is True

        # Node should be gone
        assert project_store.get_node("myproject/app/views.py::user_detail") is None

    def test_update_rewrites_child_ids(self, project_store: ProjectStore) -> None:
        """Bug regression: update() must rewrite children's IDs to use the
        store-scoped prefix instead of the snippet-local 'module::' prefix."""
        new_snippet = (
            "class User:\n"
            "    def __init__(self, name: str):\n"
            "        self.name = name\n"
            "\n"
            "    def greet(self) -> str:\n"
            '        return f"Hi, {self.name}"\n'
        )
        result = update(
            project_store,
            "myproject/app/models.py::User",
            new_snippet,
        )
        assert result.get("success") is True

        # Children should have file-path prefix, not 'module::'
        tree = subtree(project_store, "myproject/app/models.py::User", depth=2)
        assert "error" not in tree
        child_ids = [c["node_id"] for c in tree.get("children", [])]
        for cid in child_ids:
            assert cid.startswith("myproject/app/models.py::"), f"Child ID has wrong prefix: {cid}"
            assert not cid.startswith("module::"), f"Child ID still has snippet-local prefix: {cid}"

        # inspect() on a child should resolve correctly
        greet_id = "myproject/app/models.py::User::greet"
        greet = inspect(project_store, greet_id)
        assert "error" not in greet, f"inspect() failed for {greet_id}: {greet}"
        assert "greet" in greet["source"]

    def test_create_file_nested_creates_directory_nodes(self, project_store: ProjectStore) -> None:
        """Bug regression: create_file with a nested path like 'sub/utils.py'
        must create intermediate directory nodes so they're addressable."""
        result = create_file(
            project_store,
            "myproject",
            "lib/helpers/utils.py",
            source="def helper():\n    pass\n",
        )
        assert "error" not in result
        assert result["node_id"] == "myproject/lib/helpers/utils.py"

        # Intermediate directory nodes should exist
        lib_node = project_store.get_node("myproject/lib")
        assert lib_node is not None
        assert lib_node.node_type == "directory"

        helpers_node = project_store.get_node("myproject/lib/helpers")
        assert helpers_node is not None
        assert helpers_node.node_type == "directory"

        # Creating another file in the same subdir should work
        result2 = create_file(
            project_store,
            "myproject/lib/helpers",
            "extra.py",
            source="x = 1\n",
        )
        assert "error" not in result2
        assert result2["node_id"] == "myproject/lib/helpers/extra.py"

    def test_delete_file_node(self, project_store: ProjectStore) -> None:
        """Bug regression: delete must work on file nodes, not just code nodes."""
        assert project_store.get_node("myproject/app/views.py") is not None
        assert "myproject/app/views.py" in project_store.file_sources

        result = delete(project_store, "myproject/app/views.py")
        assert result.get("success") is True

        # Node and file source should be gone
        assert project_store.get_node("myproject/app/views.py") is None
        assert "myproject/app/views.py" not in project_store.file_sources
        assert "myproject/app/views.py" not in project_store.file_languages

    def test_delete_directory_node(self, project_store: ProjectStore, tmp_path: Path) -> None:
        """Bug regression: delete must work on directory nodes, removing all
        children and cleaning up file_sources/file_languages."""
        # The app/ directory has models.py and views.py
        assert project_store.get_node("myproject/app") is not None
        assert "myproject/app/models.py" in project_store.file_sources
        assert "myproject/app/views.py" in project_store.file_sources

        result = delete(project_store, "myproject/app")
        assert result.get("success") is True

        # Directory and all its files should be gone from tree
        assert project_store.get_node("myproject/app") is None
        assert project_store.get_node("myproject/app/models.py") is None
        assert project_store.get_node("myproject/app/views.py") is None

        # file_sources/file_languages cleaned up
        assert "myproject/app/models.py" not in project_store.file_sources
        assert "myproject/app/views.py" not in project_store.file_sources
        assert "myproject/app/models.py" not in project_store.file_languages
        assert "myproject/app/views.py" not in project_store.file_languages

        # config.py should be unaffected
        assert project_store.get_node("myproject/config.py") is not None
        assert "myproject/config.py" in project_store.file_sources

    def test_delete_preserves_other_files(self, project_store: ProjectStore) -> None:
        models_source_before = project_store.file_sources["myproject/app/models.py"]

        delete(project_store, "myproject/app/views.py::index")

        assert project_store.file_sources["myproject/app/models.py"] == models_source_before


# =============================================================================
# TestMultiFileProjectTools
# =============================================================================


class TestMultiFileProjectTools:
    def test_project_import_directory(self, sample_project: Path) -> None:
        store = project_import(sample_project)
        assert isinstance(store, ProjectStore)
        assert store.root is not None
        assert len(store.file_sources) == 3

    def test_project_import_file_still_returns_treestore(self, sample_project: Path) -> None:
        store = project_import(sample_project / "config.py")
        assert isinstance(store, TreeStore)

    def test_project_serialize_writes_all_files(
        self, project_store: ProjectStore, tmp_path: Path
    ) -> None:
        output_dir = tmp_path / "output"
        result = project_serialize(project_store, str(output_dir))
        assert isinstance(result, dict)
        assert "app/models.py" in result
        assert "app/views.py" in result
        assert "config.py" in result

        # Files should exist on disk
        assert (output_dir / "app" / "models.py").exists()
        assert (output_dir / "app" / "views.py").exists()
        assert (output_dir / "config.py").exists()

    def test_project_serialize_creates_directories(
        self, project_store: ProjectStore, tmp_path: Path
    ) -> None:
        output_dir = tmp_path / "fresh_output"
        project_serialize(project_store, str(output_dir))
        assert (output_dir / "app").is_dir()

    def test_roundtrip_import_serialize_import(self, sample_project: Path, tmp_path: Path) -> None:
        # Import
        store1 = project_import(sample_project)
        assert isinstance(store1, ProjectStore)

        # Serialize to new location
        output_dir = tmp_path / "roundtrip"
        project_serialize(store1, str(output_dir))

        # Re-import from serialized output
        store2 = project_import(output_dir)
        assert isinstance(store2, ProjectStore)

        # Same number of files
        assert len(store2.file_sources) == len(store1.file_sources)


# =============================================================================
# TestMultiFileServer
# =============================================================================


class TestMultiFileServer:
    def test_server_init_with_directory(self, sample_project: Path) -> None:
        server = CarveServer(file_path=sample_project)
        assert isinstance(server.store, ProjectStore)

    def test_server_init_with_env_var(
        self, sample_project: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        monkeypatch.setenv("CARVE_WORKSPACE", str(sample_project))
        server = CarveServer()
        assert isinstance(server.store, ProjectStore)
        assert server.store.root is not None

    def test_server_status_multifile(self, sample_project: Path) -> None:
        server = CarveServer(file_path=sample_project)
        status = server.status()
        assert status["loaded"] is True
        assert "file_count" in status
        assert "project_path" in status

    def test_server_import_directory(self, sample_project: Path) -> None:
        server = CarveServer(file_path=sample_project)
        assert server.store is not None
        assert isinstance(server.store, ProjectStore)
        assert len(server.store.file_sources) == 3

    def test_server_navigate_multifile(self, sample_project: Path) -> None:
        server = CarveServer(file_path=sample_project)
        result = server.subtree("myproject", depth=2)
        assert "error" not in result
        assert result["node_type"] == "directory"

    def test_server_mutate_multifile(self, sample_project: Path) -> None:
        server = CarveServer(file_path=sample_project)
        result = server.update(
            "myproject/app/views.py::index",
            'def index():\n    return "Updated"\n',
        )
        assert result.get("success") is True

    def test_server_serialize_multifile(self, sample_project: Path) -> None:
        server = CarveServer(file_path=sample_project)
        result = server.serialize()
        assert result.get("success") is True
        assert "file_count" in result


# =============================================================================
# TestBackwardCompat
# =============================================================================


class TestBackwardCompat:
    def test_single_file_import_unchanged(self, sample_project: Path) -> None:
        store = project_import(sample_project / "config.py")
        assert isinstance(store, TreeStore)
        assert store.language == "python"

    def test_single_file_tools_unchanged(self, sample_project: Path) -> None:
        store = project_import(sample_project / "app" / "models.py")
        assert isinstance(store, TreeStore)

        # Navigation
        result = subtree(store, "module", depth=1)
        assert "error" not in result

        result_search = search(store, pattern="User")
        assert len(result_search) > 0

    def test_single_file_serialize_unchanged(self, sample_project: Path) -> None:
        store = project_import(sample_project / "config.py")
        assert isinstance(store, TreeStore)

        result = project_serialize(store)
        assert isinstance(result, str)
        assert "DATABASE_URL" in result

    def test_server_single_file_unchanged(self, sample_project: Path) -> None:
        server = CarveServer(file_path=sample_project / "config.py")
        assert isinstance(server.store, TreeStore)
        status = server.status()
        assert "language" in status
        assert "file_path" in status
