"""Integration tests for Lattice Python SDK client.

Tests the high-level API:
- get_instincts(): Read merged Global + Project rules
- search(): Hybrid search (FTS5 + optional vector + RRF)
- log_turn(): Store conversation turns
- propose_rule(): Submit rule proposals

Reference: RFC-002 §6.4 Python SDK
"""

import tempfile
from pathlib import Path

import pytest
from returns.result import Failure

from lattice import Client, PatternType, RuleProposal, SearchResult


class TestClientInit:
    """Test client initialization."""

    def test_init_with_path(self):
        """Client should accept a path string."""
        client = Client("/tmp/test")
        assert client._project_path == Path("/tmp/test")

    def test_init_with_pathlib(self):
        """Client should accept a Path object."""
        client = Client(Path("/tmp/test"))
        assert client._project_path == Path("/tmp/test")

    def test_init_default_cwd(self):
        """Client should default to current directory."""
        client = Client()
        assert client._project_path == Path.cwd()


class TestGetInstincts:
    """Test get_instincts method."""

    def test_empty_when_no_rules(self):
        """Should return empty string when no rules exist."""
        with tempfile.TemporaryDirectory() as tmpdir:
            client = Client(tmpdir)
            result = client.get_instincts()
            assert result == ""

    def test_returns_project_rules(self):
        """Should return project rules from .lattice/rules/."""
        with tempfile.TemporaryDirectory() as tmpdir:
            lattice_dir = Path(tmpdir) / ".lattice"
            rules_dir = lattice_dir / "rules"
            rules_dir.mkdir(parents=True)

            (rules_dir / "conventions.md").write_text(
                "# Conventions\n\nUse type hints.\n"
            )

            client = Client(tmpdir)
            result = client.get_instincts()

            assert "Conventions" in result
            assert "Use type hints" in result

    def test_returns_global_rules(self):
        """Should return global rules from ~/.lattice/rules/."""
        with tempfile.TemporaryDirectory() as tmpdir:
            # Create a fake global rules dir (separate from project)
            global_dir = Path(tmpdir) / "global_lattice"
            global_rules_dir = global_dir / "rules"
            global_rules_dir.mkdir(parents=True)

            (global_rules_dir / "global.md").write_text(
                "# Global Rules\n\nBe concise.\n"
            )

            # Create separate project dir (no project rules)
            project_dir = Path(tmpdir) / "project"

            client = Client(project_dir, global_path=global_dir)
            result = client.get_instincts()

            assert "Global:" in result
            assert "Be concise" in result

    def test_skips_promoted_rules(self):
        """Should skip rules with lattice:promoted marker."""
        with tempfile.TemporaryDirectory() as tmpdir:
            lattice_dir = Path(tmpdir) / ".lattice"
            rules_dir = lattice_dir / "rules"
            rules_dir.mkdir(parents=True)

            (rules_dir / "regular.md").write_text("# Regular\n\nNormal rule.\n")
            (rules_dir / "promoted.md").write_text(
                "# Promoted\n\n<!-- lattice:promoted -->\nPromoted rule.\n"
            )

            client = Client(tmpdir)
            result = client.get_instincts()

            assert "Regular" in result
            assert "Promoted" not in result

    def test_merges_global_and_project(self):
        """Should merge both global and project rules."""
        with tempfile.TemporaryDirectory() as tmpdir:
            # Global rules
            global_dir = Path(tmpdir) / "global"
            global_rules_dir = global_dir / "rules"
            global_rules_dir.mkdir(parents=True)
            (global_rules_dir / "global.md").write_text("Global rule.\n")

            # Project rules
            project_dir = Path(tmpdir) / "project"
            lattice_dir = project_dir / ".lattice"
            rules_dir = lattice_dir / "rules"
            rules_dir.mkdir(parents=True)
            (rules_dir / "project.md").write_text("Project rule.\n")

            client = Client(project_dir, global_path=global_dir)
            result = client.get_instincts()

            assert "Global rule" in result
            assert "Project rule" in result


class TestSearch:
    """Test search method."""

    def test_search_fails_without_store(self):
        """Search should fail if no store.db exists."""
        with tempfile.TemporaryDirectory() as tmpdir:
            client = Client(tmpdir)
            result = client.search("test query")
            assert isinstance(result, Failure)
            assert "initialized" in result.failure()

    def test_search_scope_project(self):
        """Search with project scope."""
        with tempfile.TemporaryDirectory() as tmpdir:
            from lattice.shell.schema import create_store
            from lattice.shell.store import insert_log
            from lattice.core.types.enums import Role

            # Create store
            lattice_dir = Path(tmpdir) / ".lattice"
            lattice_dir.mkdir(parents=True)
            store_path = lattice_dir / "store.db"
            conn = create_store(store_path).unwrap()

            # Insert test data
            insert_log(conn, "session-1", Role.USER, "Test message about Python", None)
            insert_log(conn, "session-1", Role.ASSISTANT, "Python is great", None)
            conn.close()

            client = Client(tmpdir)
            result = client.search("Python")

            # Should return results
            assert isinstance(result.unwrap(), list)
            assert len(result.unwrap()) == 2


class TestLogTurn:
    """Test log_turn method."""

    def test_log_turn_fails_without_store(self):
        """log_turn should fail if no store.db exists."""
        with tempfile.TemporaryDirectory() as tmpdir:
            client = Client(tmpdir)
            result = client.log_turn("Hello", "Hi there!")
            assert isinstance(result.failure(), str)

    def test_log_turn_creates_entries(self):
        """log_turn should create user and assistant entries."""
        with tempfile.TemporaryDirectory() as tmpdir:
            from lattice.shell.schema import create_store

            # Create store
            lattice_dir = Path(tmpdir) / ".lattice"
            lattice_dir.mkdir(parents=True)
            store_path = lattice_dir / "store.db"
            conn = create_store(store_path).unwrap()
            conn.close()

            client = Client(tmpdir)
            result = client.log_turn(
                user="What is Python?",
                assistant="Python is a programming language.",
                tools_called=["search"],
            )

            # Should succeed and return log_id > 0
            assert result.unwrap() > 0


class TestProposeRule:
    """Test propose_rule method."""

    def test_propose_rule_fails_without_lattice(self):
        """propose_rule should fail if .lattice doesn't exist."""
        with tempfile.TemporaryDirectory() as tmpdir:
            client = Client(tmpdir)
            result = client.propose_rule(
                pattern=PatternType.CONVENTION,
                observation="Always use type hints",
                evidence=["session-1"],
                suggested_action="add-type-hints",
            )
            assert isinstance(result.failure(), str)

    def test_propose_rule_validates_observation_length(self):
        """propose_rule should validate observation length."""
        with tempfile.TemporaryDirectory() as tmpdir:
            # Create .lattice/drift/proposals
            lattice_dir = Path(tmpdir) / ".lattice"
            proposals_dir = lattice_dir / "drift" / "proposals"
            proposals_dir.mkdir(parents=True)

            client = Client(tmpdir)
            result = client.propose_rule(
                pattern=PatternType.CONVENTION,
                observation="x" * 201,  # Too long
                evidence=["session-1"],
                suggested_action="action",
            )
            assert isinstance(result.failure(), str)
            assert "200" in result.failure()

    def test_propose_rule_validates_action_length(self):
        """propose_rule should validate suggested_action length."""
        with tempfile.TemporaryDirectory() as tmpdir:
            lattice_dir = Path(tmpdir) / ".lattice"
            proposals_dir = lattice_dir / "drift" / "proposals"
            proposals_dir.mkdir(parents=True)

            client = Client(tmpdir)
            result = client.propose_rule(
                pattern=PatternType.CONVENTION,
                observation="Test",
                evidence=["session-1"],
                suggested_action="x" * 101,  # Too long
            )
            assert isinstance(result.failure(), str)
            assert "100" in result.failure()

    def test_propose_rule_validates_pattern_type(self):
        """propose_rule should validate pattern type."""
        with tempfile.TemporaryDirectory() as tmpdir:
            lattice_dir = Path(tmpdir) / ".lattice"
            proposals_dir = lattice_dir / "drift" / "proposals"
            proposals_dir.mkdir(parents=True)

            client = Client(tmpdir)
            result = client.propose_rule(
                pattern="invalid-pattern",  # Invalid
                observation="Test",
                evidence=["session-1"],
                suggested_action="action",
            )
            assert isinstance(result.failure(), str)
            assert "Invalid pattern" in result.failure()

    def test_propose_rule_creates_file(self):
        """propose_rule should create a proposal file."""
        with tempfile.TemporaryDirectory() as tmpdir:
            lattice_dir = Path(tmpdir) / ".lattice"
            proposals_dir = lattice_dir / "drift" / "proposals"
            proposals_dir.mkdir(parents=True)

            client = Client(tmpdir)
            result = client.propose_rule(
                pattern=PatternType.BUG_PATTERN,
                observation="Null pointer in auth flow",
                evidence=["session-1", "session-2"],
                suggested_action="add-null-check",
            )

            assert isinstance(result.unwrap(), str)
            # Verify file was created
            files = list(proposals_dir.glob("proposal_*.md"))
            assert len(files) == 1

            content = files[0].read_text()
            assert "bug-pattern" in content
            assert "Null pointer in auth flow" in content
            assert "session-1" in content
            assert "add-null-check" in content

    def test_propose_rule_accepts_string_pattern(self):
        """propose_rule should accept string pattern."""
        with tempfile.TemporaryDirectory() as tmpdir:
            lattice_dir = Path(tmpdir) / ".lattice"
            proposals_dir = lattice_dir / "drift" / "proposals"
            proposals_dir.mkdir(parents=True)

            client = Client(tmpdir)
            result = client.propose_rule(
                pattern="preference",  # String instead of enum
                observation="Prefer snake_case",
                evidence=["session-1"],
                suggested_action="use-snake-case",
            )

            assert isinstance(result.unwrap(), str)

    def test_propose_rule_accepts_rule_proposal_object(self):
        """propose_rule should accept RuleProposal object."""
        with tempfile.TemporaryDirectory() as tmpdir:
            lattice_dir = Path(tmpdir) / ".lattice"
            proposals_dir = lattice_dir / "drift" / "proposals"
            proposals_dir.mkdir(parents=True)

            client = Client(tmpdir)
            proposal = RuleProposal(
                pattern=PatternType.BUG_PATTERN,
                observation="Null pointer in auth flow",
                evidence=["session-1", "session-2"],
                suggested_action="add-null-check",
            )
            result = client.propose_rule(proposal)

            assert isinstance(result.unwrap(), str)
            # Verify file was created
            files = list(proposals_dir.glob("proposal_*.md"))
            assert len(files) == 1

            content = files[0].read_text()
            assert "bug-pattern" in content
            assert "Null pointer in auth flow" in content
            assert "session-1" in content
            assert "add-null-check" in content


class TestPatternType:
    """Test PatternType enum."""

    def test_pattern_type_values(self):
        """PatternType should have correct values."""
        assert PatternType.CONVENTION.value == "convention"
        assert PatternType.BUG_PATTERN.value == "bug-pattern"
        assert PatternType.PREFERENCE.value == "preference"
        assert PatternType.ARCHITECTURE.value == "architecture"

    def test_pattern_type_from_string(self):
        """PatternType should be creatable from string."""
        assert PatternType("convention") == PatternType.CONVENTION
        assert PatternType("preference") == PatternType.PREFERENCE


class TestSearchResult:
    """Test SearchResult dataclass."""

    def test_search_result_creation(self):
        """SearchResult should be creatable with all fields."""
        result = SearchResult(
            session_id="session-123",
            role="user",
            content="Test content",
            rrf_score=0.85,
            timestamp="2026-01-01T00:00:00Z",
        )
        assert result.session_id == "session-123"
        assert result.role == "user"
        assert result.content == "Test content"
        assert result.rrf_score == 0.85
        assert result.timestamp == "2026-01-01T00:00:00Z"

    def test_search_result_frozen(self):
        """SearchResult should be immutable."""
        result = SearchResult(
            session_id="session-123",
            role="user",
            content="Test",
            rrf_score=0.5,
            timestamp="2026-01-01",
        )
        with pytest.raises(AttributeError):
            result.session_id = "new-id"


class TestRuleProposal:
    """Test RuleProposal dataclass."""

    def test_rule_proposal_creation(self):
        """RuleProposal should be creatable with all fields."""
        proposal = RuleProposal(
            pattern=PatternType.BUG_PATTERN,
            observation="Test observation",
            evidence=["session-1"],
            suggested_action="test-action",
        )
        assert proposal.pattern == PatternType.BUG_PATTERN
        assert proposal.observation == "Test observation"
        assert proposal.evidence == ["session-1"]
        assert proposal.suggested_action == "test-action"

    def test_rule_proposal_frozen(self):
        """RuleProposal should be immutable."""
        proposal = RuleProposal(
            pattern=PatternType.CONVENTION,
            observation="Test",
            evidence=[],
            suggested_action="action",
        )
        with pytest.raises(AttributeError):
            proposal.observation = "new"
