"""End-to-end test for multi-project flow.

Tests the complete multi-project Lattice workflow:
1. Multiple project isolation - separate stores
2. Global rules overlay - project + global rules
3. Global search scope - querying across projects

Reference: RFC-002 §2.3 The Overlay (Global vs. Project)
"""

from pathlib import Path

import pytest
from returns.result import Failure, Success

from lattice import Client
from lattice.core.types.enums import Role
from lattice.shell.schema import create_global_store, create_store
from lattice.shell.store import generate_session_id, insert_log


class TestMultiProjectIsolation:
    """Test that multiple projects have isolated data stores."""

    @pytest.fixture
    def multi_project_setup(self, tmp_path: Path) -> tuple[Path, Path, Path]:
        """Create two projects with their own stores."""
        # Project A
        project_a = tmp_path / "project_a"
        project_a.mkdir()
        lattice_a = project_a / ".lattice"
        lattice_a.mkdir()
        (lattice_a / "rules").mkdir()
        (lattice_a / "drift" / "proposals").mkdir(parents=True)
        (lattice_a / "config.toml").write_text('[compiler]\nmodel = "test"\n')

        result_a = create_store(lattice_a / "store.db")
        assert isinstance(result_a, Success)
        result_a.unwrap().close()

        # Project B
        project_b = tmp_path / "project_b"
        project_b.mkdir()
        lattice_b = project_b / ".lattice"
        lattice_b.mkdir()
        (lattice_b / "rules").mkdir()
        (lattice_b / "drift" / "proposals").mkdir(parents=True)
        (lattice_b / "config.toml").write_text('[compiler]\nmodel = "test"\n')

        result_b = create_store(lattice_b / "store.db")
        assert isinstance(result_b, Success)
        result_b.unwrap().close()

        # Global directory
        global_dir = tmp_path / "global"
        global_dir.mkdir()
        (global_dir / "rules").mkdir(parents=True)

        return project_a, project_b, global_dir

    def _ingest_session(
        self, project_dir: Path, session_id: str, messages: list[tuple[str, str]]
    ) -> None:
        """Ingest a conversation session into the project's store."""
        lattice_dir = project_dir / ".lattice"
        store_path = lattice_dir / "store.db"

        result = create_store(store_path)
        assert isinstance(result, Success)
        conn = result.unwrap()

        try:
            for role_str, content in messages:
                role = Role.USER if role_str == "user" else Role.ASSISTANT
                insert_result = insert_log(conn, session_id, role, content, None)
                assert isinstance(insert_result, Success)
        finally:
            conn.close()

    def test_projects_have_isolated_stores(
        self, multi_project_setup: tuple[Path, Path, Path]
    ) -> None:
        """Each project has its own isolated store."""
        project_a, project_b, global_dir = multi_project_setup

        # Ingest different data into each project
        session_a = generate_session_id()
        self._ingest_session(
            project_a,
            session_a,
            [
                ("user", "Implement REST API in project A"),
                ("assistant", "Created REST endpoints for project A."),
            ],
        )

        session_b = generate_session_id()
        self._ingest_session(
            project_b,
            session_b,
            [
                ("user", "Implement GraphQL API in project B"),
                ("assistant", "Created GraphQL schema for project B."),
            ],
        )

        # Verify project A only sees its data
        client_a = Client(project_a, global_path=global_dir)
        result_a = client_a.search("API")
        assert isinstance(result_a, Success)
        results_a = result_a.unwrap()
        assert any("REST" in r.content for r in results_a)
        assert not any("GraphQL" in r.content for r in results_a), (
            "Project A should not see Project B data"
        )

        # Verify project B only sees its data
        client_b = Client(project_b, global_path=global_dir)
        result_b = client_b.search("API")
        assert isinstance(result_b, Success)
        results_b = result_b.unwrap()
        assert any("GraphQL" in r.content for r in results_b)
        assert not any("REST" in r.content for r in results_b), (
            "Project B should not see Project A data"
        )

    def test_projects_have_isolated_rules(
        self, multi_project_setup: tuple[Path, Path, Path]
    ) -> None:
        """Each project has its own rules directory."""
        project_a, project_b, global_dir = multi_project_setup

        # Create project-specific rules
        rules_a = project_a / ".lattice" / "rules"
        (rules_a / "conventions.md").write_text(
            "# Project A Conventions\n\nUse snake_case for variables.\n"
        )

        rules_b = project_b / ".lattice" / "rules"
        (rules_b / "conventions.md").write_text(
            "# Project B Conventions\n\nUse camelCase for variables.\n"
        )

        # Verify each project sees only its rules
        client_a = Client(project_a, global_path=global_dir)
        instincts_a = client_a.get_instincts()
        assert "Project A" in instincts_a
        assert "snake_case" in instincts_a
        assert "camelCase" not in instincts_a

        client_b = Client(project_b, global_path=global_dir)
        instincts_b = client_b.get_instincts()
        assert "Project B" in instincts_b
        assert "camelCase" in instincts_b
        assert "snake_case" not in instincts_b


class TestGlobalRulesOverlay:
    """Test the global rules overlay (Project overrides Global)."""

    @pytest.fixture
    def overlay_setup(self, tmp_path: Path) -> tuple[Path, Path]:
        """Create a project with global rules."""
        # Project
        project = tmp_path / "project"
        project.mkdir()
        lattice_dir = project / ".lattice"
        lattice_dir.mkdir()
        (lattice_dir / "rules").mkdir()
        (lattice_dir / "config.toml").write_text('[compiler]\nmodel = "test"\n')

        result = create_store(lattice_dir / "store.db")
        assert isinstance(result, Success)
        result.unwrap().close()

        # Global directory with rules
        global_dir = tmp_path / "global"
        global_dir.mkdir()
        global_rules = global_dir / "rules"
        global_rules.mkdir(parents=True)
        (global_rules / "identity.md").write_text(
            "# Identity\n\nYou are a helpful assistant.\n"
        )
        (global_rules / "preferences.md").write_text(
            "# Global Preferences\n\nAlways use type hints.\n"
        )

        return project, global_dir

    def test_global_rules_included_in_instincts(
        self, overlay_setup: tuple[Path, Path]
    ) -> None:
        """Global rules are combined with project rules in instincts."""
        project, global_dir = overlay_setup

        # Add project-specific rule
        project_rules = project / ".lattice" / "rules"
        (project_rules / "testing.md").write_text(
            "# Testing\n\nUse pytest for all tests.\n"
        )

        client = Client(project, global_path=global_dir)
        instincts = client.get_instincts()

        # Should include global rules
        assert "helpful assistant" in instincts
        assert "type hints" in instincts

        # Should include project rules
        assert "pytest" in instincts

    def test_project_rule_overrides_global(
        self, overlay_setup: tuple[Path, Path]
    ) -> None:
        """Project rules take precedence over global rules on same topic."""
        project, global_dir = overlay_setup

        # Global has "Use type hints"
        # Project has specific preference
        project_rules = project / ".lattice" / "rules"
        (project_rules / "preferences.md").write_text(
            "# Project Preferences\n\nUse runtime type checking instead of type hints.\n"
        )

        client = Client(project, global_path=global_dir)
        instincts = client.get_instincts()

        # Both appear (concatenation strategy per RFC-002)
        # The consuming LLM handles precedence by topic
        assert "type hints" in instincts
        assert "runtime type checking" in instincts

    def test_no_global_rules_still_works(
        self, overlay_setup: tuple[Path, Path]
    ) -> None:
        """Project works without global rules directory."""
        project, _ = overlay_setup

        # Use non-existent global path
        empty_global = project.parent / "no_global"
        empty_global.mkdir(exist_ok=True)

        client = Client(project, global_path=empty_global)
        instincts = client.get_instincts()

        # Should have empty instincts (no rules)
        # But should not crash
        assert isinstance(instincts, str)


class TestGlobalSearchScope:
    """Test searching with global scope."""

    @pytest.fixture
    def global_search_setup(self, tmp_path: Path) -> tuple[Path, Path]:
        """Create a project and global store."""
        # Project
        project = tmp_path / "project"
        project.mkdir()
        lattice_dir = project / ".lattice"
        lattice_dir.mkdir()
        (lattice_dir / "rules").mkdir()
        (lattice_dir / "config.toml").write_text('[compiler]\nmodel = "test"\n')

        result = create_store(lattice_dir / "store.db")
        assert isinstance(result, Success)
        result.unwrap().close()

        # Global directory with global.db
        global_dir = tmp_path / "global"
        global_dir.mkdir()
        (global_dir / "rules").mkdir(parents=True)

        # Create global.db with evidence
        global_db_path = global_dir / "store" / "global.db"
        global_db_path.parent.mkdir(parents=True, exist_ok=True)
        global_result = create_global_store(global_db_path)
        assert isinstance(global_result, Success)
        global_result.unwrap().close()

        return project, global_dir

    def test_global_search_requires_global_store(
        self, global_search_setup: tuple[Path, Path]
    ) -> None:
        """Global search returns failure if global.db doesn't exist."""
        project, global_dir = global_search_setup

        # Remove global.db
        global_db = global_dir / "store" / "global.db"
        if global_db.exists():
            global_db.unlink()

        client = Client(project, global_path=global_dir)
        result = client.search("test query", scope="global")

        assert isinstance(result, Failure)
        assert "Global store not found" in result.failure()

    def test_project_search_uses_project_store(
        self, global_search_setup: tuple[Path, Path]
    ) -> None:
        """Default search scope queries project store, not global."""
        project, global_dir = global_search_setup

        client = Client(project, global_path=global_dir)

        # Project store is empty, search should return empty results
        result = client.search("anything", scope="project")
        assert isinstance(result, Success)
        assert result.unwrap() == []
