"""Tests for newly wired MCP tools — gm_code, gm_security, gm_cluster.

Also covers enhanced gm_context bootstrap and gm_app_dispatch lifecycle.
Validates that the MCP tool layer correctly delegates to core modules.
"""
import json
import tempfile
from pathlib import Path

import pytest

_has_sqlite_vec = True
try:
    import sqlite_vec  # noqa: F401
except ImportError:
    _has_sqlite_vec = False


# =========================================================================
# gm_security tool
# =========================================================================


class TestGmSecurityScan:
    """Test the gm_security scan/redact/policy_check actions."""

    def test_scan_detects_secrets(self):
        from greymatter_core.scanner import SecretScanner
        scanner = SecretScanner()
        detections = scanner.scan("key=AKIAIOSFODNN7EXAMPLE")
        assert len(detections) >= 1
        assert any(d.pattern_name == "aws_access_key" for d in detections)

    def test_scan_clean_text(self):
        from greymatter_core.scanner import SecretScanner
        scanner = SecretScanner()
        detections = scanner.scan("Normal text without secrets")
        assert len(detections) == 0

    def test_redact_replaces_secrets(self):
        from greymatter_core.scanner import SecretScanner
        scanner = SecretScanner()
        redacted = scanner.redact("key=AKIAIOSFODNN7EXAMPLE")
        assert "AKIAIOSFODNN7EXAMPLE" not in redacted
        assert "REDACTED" in redacted

    def test_scan_file_content(self, tmp_path):
        """Scanner can process file contents."""
        from greymatter_core.scanner import SecretScanner
        env_file = tmp_path / "test.env"
        env_file.write_text("AWS_KEY=AKIAIOSFODNN7EXAMPLE\nNORMAL=hello")

        scanner = SecretScanner()
        content = env_file.read_text()
        detections = scanner.scan(content)
        assert len(detections) >= 1


class TestGmSecurityPolicy:
    """Test policy evaluation via the security tool."""

    def test_policy_evaluate_allow(self):
        from greymatter_core.policy import PolicyEngine, PolicyRule, PolicyOutcome, EvalContext
        engine = PolicyEngine([
            PolicyRule(rule_id="allow-all", description="Allow", outcome=PolicyOutcome.ALLOW, priority=999),
        ])
        ctx = EvalContext(content="clean text")
        decision = engine.evaluate(ctx)
        assert decision.outcome == PolicyOutcome.ALLOW

    def test_policy_evaluate_refuse(self):
        from greymatter_core.policy import (
            PolicyEngine, PolicyRule, PolicyOutcome, Condition, EvalContext,
        )
        engine = PolicyEngine([
            PolicyRule(
                rule_id="block-test",
                description="Block test",
                outcome=PolicyOutcome.REFUSE,
                priority=10,
                conditions=[Condition(field="content", operator="contains", value="BLOCKED")],
                reason="Contains blocked content",
            ),
        ])
        ctx = EvalContext(content="This is BLOCKED content")
        decision = engine.evaluate(ctx)
        assert decision.outcome == PolicyOutcome.REFUSE

    def test_policy_from_yaml(self):
        from greymatter_core.policy import PolicyEngine
        yaml_text = """
rules:
  - rule_id: test
    description: Test
    outcome: allow
    priority: 100
"""
        engine = PolicyEngine.from_yaml(yaml_text)
        assert len(engine.rules) == 1


@pytest.mark.skipif(not _has_sqlite_vec, reason="sqlite_vec not installed")
class TestGmSecurityAudit:
    """Test audit log functionality."""

    def test_audit_log_insert_and_query(self, tmp_path):
        from greymatter_core.db import SQLiteDatabase
        db = SQLiteDatabase(str(tmp_path / "audit.db"))

        db._conn.execute(
            "INSERT INTO security_events (event_type, severity, action_taken) VALUES (?, ?, ?)",
            ("test_event", "low", "logged"),
        )
        db._conn.commit()

        events = db._conn.execute("SELECT * FROM security_events").fetchall()
        assert len(events) == 1


# =========================================================================
# gm_code tool
# =========================================================================


class TestGmCodeModels:
    """Test code symbol models."""

    def test_code_symbol_id(self):
        from greymatter_code.models import CodeSymbol
        sym = CodeSymbol(
            repo="test-repo",
            file_path="src/main.py",
            language="python",
            kind="function",
            name="hello",
            qualified_name="main.hello",
            signature="def hello(name: str) -> str",
            byte_start=0,
            byte_end=100,
        )
        assert "test-repo" in sym.id
        assert "main.hello" in sym.id
        assert "function" in sym.id

    def test_code_symbol_parse_id(self):
        from greymatter_code.models import CodeSymbol
        sym = CodeSymbol(
            repo="myrepo", file_path="app.py", language="python",
            kind="class", name="App", qualified_name="app.App",
            signature="class App:", byte_start=0, byte_end=50,
        )
        parsed = CodeSymbol.parse_id(sym.id)
        assert parsed["repo"] == "myrepo"
        assert parsed["kind"] == "class"

    def test_symbol_link_creation(self):
        from greymatter_code.models import SymbolLink
        link = SymbolLink(
            symbol_id="test::file.py::func#function",
            target_type="knowledge",
            target_id="k-123",
            relationship="implements",
        )
        assert link.target_type == "knowledge"
        assert link.relationship == "implements"


class TestGmCodeDB:
    """Test CodeDB operations."""

    def test_insert_and_search(self):
        from greymatter_code.db import CodeDB
        from greymatter_code.models import CodeSymbol

        with tempfile.TemporaryDirectory() as tmpdir:
            db = CodeDB(str(Path(tmpdir) / "code.db"))
            sym = CodeSymbol(
                repo="test", file_path="main.py", language="python",
                kind="function", name="process_data",
                qualified_name="main.process_data",
                signature="def process_data(items: list) -> dict",
                byte_start=0, byte_end=200,
            )
            db.insert_symbol(sym)

            results = db.search_symbols(query="process")
            assert len(results) >= 1
            assert results[0]["name"] == "process_data"
            db.close()

    def test_count_and_delete(self):
        from greymatter_code.db import CodeDB
        from greymatter_code.models import CodeSymbol

        with tempfile.TemporaryDirectory() as tmpdir:
            db = CodeDB(str(Path(tmpdir) / "code.db"))
            for i in range(5):
                sym = CodeSymbol(
                    repo="myrepo", file_path=f"file{i}.py", language="python",
                    kind="function", name=f"func_{i}",
                    qualified_name=f"file{i}.func_{i}",
                    signature=f"def func_{i}():", byte_start=0, byte_end=10,
                )
                db.insert_symbol(sym)

            assert db.count_symbols() == 5
            assert db.count_symbols(repo="myrepo") == 5

            deleted = db.delete_repo_symbols("myrepo")
            assert deleted == 5
            assert db.count_symbols() == 0
            db.close()

    def test_symbol_links(self):
        from greymatter_code.db import CodeDB
        from greymatter_code.models import CodeSymbol, SymbolLink

        with tempfile.TemporaryDirectory() as tmpdir:
            db = CodeDB(str(Path(tmpdir) / "code.db"))
            sym = CodeSymbol(
                repo="r", file_path="f.py", language="python",
                kind="function", name="fn", qualified_name="f.fn",
                signature="def fn():", byte_start=0, byte_end=10,
            )
            db.insert_symbol(sym)

            link = SymbolLink(
                symbol_id=sym.id, target_type="knowledge",
                target_id="k-1", relationship="implements",
            )
            db.link_symbol(link)

            links = db.get_symbol_links(sym.id)
            assert len(links) == 1
            assert links[0]["target_type"] == "knowledge"

            reverse = db.get_links_to_target("knowledge", "k-1")
            assert len(reverse) == 1
            db.close()


class TestGmCodeRegistry:
    """Test language registry."""

    def test_registry_basics(self):
        from greymatter_code.registry import LanguageRegistry
        reg = LanguageRegistry()
        # Initially empty
        assert len(reg.languages) == 0

    def test_global_registry_discovers(self):
        from greymatter_code.registry import get_registry
        reg = get_registry()
        # Should have discovered language plugins
        assert isinstance(reg.languages, list)


# =========================================================================
# gm_cluster tool
# =========================================================================


class TestGmClusterState:
    """Test cluster state machine."""

    def test_initial_state_is_solo(self):
        from greymatter_core.cluster.state import NodeStateMachine, NodeState
        sm = NodeStateMachine()
        assert sm.state == NodeState.SOLO
        assert not sm.is_clustered

    def test_valid_transition(self):
        from greymatter_core.cluster.state import NodeStateMachine, NodeState
        sm = NodeStateMachine()
        assert sm.can_transition(NodeState.JOINING)
        sm.transition(NodeState.JOINING)
        assert sm.state == NodeState.JOINING

    def test_invalid_transition_raises(self):
        from greymatter_core.cluster.state import NodeStateMachine, NodeState
        sm = NodeStateMachine()
        # Can't go from SOLO directly to SPOKE
        assert not sm.can_transition(NodeState.SPOKE)

    def test_transition_history(self):
        from greymatter_core.cluster.state import NodeStateMachine, NodeState
        sm = NodeStateMachine()
        sm.transition(NodeState.JOINING)
        sm.transition(NodeState.SPOKE)
        assert len(sm.history) == 2
        assert sm.is_clustered

    def test_state_to_dict(self):
        from greymatter_core.cluster.state import NodeStateMachine
        sm = NodeStateMachine()
        d = sm.to_dict()
        assert "state" in d
        assert d["state"] == "solo"


class TestGmClusterHealth:
    """Test cluster health checks."""

    @pytest.mark.skipif(not _has_sqlite_vec, reason="sqlite_vec not installed")
    def test_node_health_check(self, tmp_path):
        from greymatter_core.db import SQLiteDatabase
        from greymatter_core.cluster.health import HealthChecker

        db = SQLiteDatabase(str(tmp_path / "health.db"))
        checker = HealthChecker(db, db_path=str(tmp_path / "health.db"))
        report = checker.check_node()

        assert report.status is not None
        assert len(report.checks) >= 1

    def test_health_status_values(self):
        from greymatter_core.cluster.health import HealthStatus
        assert HealthStatus.HEALTHY.value == "healthy"
        assert HealthStatus.DEGRADED.value == "degraded"
        assert HealthStatus.UNHEALTHY.value == "unhealthy"

    def test_sync_lag_computation(self):
        from greymatter_core.cluster.health import compute_sync_lag
        assert compute_sync_lag(100, 95) == 5
        assert compute_sync_lag(100, 100) == 0


class TestGmClusterDiscovery:
    """Test endpoint discovery."""

    def test_cluster_endpoint_url(self):
        from greymatter_core.cluster.discovery import ClusterEndpoint
        ep = ClusterEndpoint(address="192.168.1.10", port=8000, name="hub")
        assert ep.url == "http://192.168.1.10:8000"

    def test_endpoint_to_dict(self):
        from greymatter_core.cluster.discovery import ClusterEndpoint
        ep = ClusterEndpoint(address="10.0.0.1", port=9000, name="test", source="manual")
        d = ep.to_dict()
        assert d["address"] == "10.0.0.1"
        assert d["source"] == "manual"

    def test_manual_discovery_missing_config(self):
        from greymatter_core.cluster.discovery import discover_manual
        # Non-existent config should return empty
        result = discover_manual("/tmp/nonexistent_cluster_config.json")
        assert result == []


# =========================================================================
# gm_context bootstrap
# =========================================================================


@pytest.mark.skipif(not _has_sqlite_vec, reason="sqlite_vec not installed")
class TestGmContextBootstrap:
    """Test enhanced context engine."""

    def test_build_session_context(self, tmp_path):
        from greymatter_core.db import SQLiteDatabase
        from greymatter_core.documents import DocumentStore
        from greymatter_core.standards import StandardsManager
        from greymatter_core.patterns import PatternManager
        from greymatter_core.context import ContextEngine

        db = SQLiteDatabase(str(tmp_path / "ctx.db"))
        docs = DocumentStore(db)
        standards = StandardsManager(db)
        pats = PatternManager(db)
        engine = ContextEngine(db, docs, standards, pats)

        ctx = engine.build_session_context(max_tokens=2000)
        assert "quality_standards" in ctx
        assert "active_patterns" in ctx

    def test_context_with_project_filter(self, tmp_path):
        from greymatter_core.db import SQLiteDatabase
        from greymatter_core.documents import DocumentStore
        from greymatter_core.standards import StandardsManager
        from greymatter_core.patterns import PatternManager
        from greymatter_core.context import ContextEngine

        db = SQLiteDatabase(str(tmp_path / "ctx.db"))
        docs = DocumentStore(db)
        standards = StandardsManager(db)
        pats = PatternManager(db)
        engine = ContextEngine(db, docs, standards, pats)

        ctx = engine.build_session_context(project="test-project", max_tokens=1000)
        assert isinstance(ctx, dict)


# =========================================================================
# gm_app_dispatch lifecycle
# =========================================================================


@pytest.mark.skipif(not _has_sqlite_vec, reason="sqlite_vec not installed")
class TestGmAppDispatchLifecycle:
    """Test agent dispatch with lifecycle tracking."""

    def test_create_and_complete_work_item(self, tmp_path):
        from greymatter_core.db import SQLiteDatabase
        from greymatter_core.work_items import WorkItemManager

        db = SQLiteDatabase(str(tmp_path / "dispatch.db"))
        wm = WorkItemManager(db)

        item = wm.create(
            title="test:agent - Run analysis",
            description="Analyze data",
            domain_tags="testing",
        )
        assert item["status"] == "queued"

        wm.complete(item["id"])
        completed = wm.get(item["id"])
        assert completed["status"] == "completed"

    def test_work_item_lifecycle(self, tmp_path):
        from greymatter_core.db import SQLiteDatabase
        from greymatter_core.work_items import WorkItemManager

        db = SQLiteDatabase(str(tmp_path / "lifecycle.db"))
        wm = WorkItemManager(db)

        item = wm.create(title="Lifecycle test", description="Test full lifecycle")

        # Start
        wm.update_status(item["id"], "in_progress")
        assert wm.get(item["id"])["status"] == "in_progress"

        # Review notes
        wm.update_review(item["id"], "in_progress", "Agent completed successfully")
        updated = wm.get(item["id"])
        assert "successfully" in updated.get("review_notes", "")

        # Complete
        wm.complete(item["id"])
        assert wm.get(item["id"])["status"] == "completed"

    def test_list_active_tasks(self, tmp_path):
        from greymatter_core.db import SQLiteDatabase
        from greymatter_core.work_items import WorkItemManager

        db = SQLiteDatabase(str(tmp_path / "active.db"))
        wm = WorkItemManager(db)

        wm.create(title="Active task", description="In progress")
        wm.create(title="Another task", description="Also active")

        items = wm.list(status="queued")
        assert len(items) >= 2


# =========================================================================
# Sync tracking (for cluster)
# =========================================================================


@pytest.mark.skipif(not _has_sqlite_vec, reason="sqlite_vec not installed")
class TestSyncTracking:
    """Test sync version tracking for cluster operations."""

    def test_node_id_exists(self, tmp_path):
        from greymatter_core.db import SQLiteDatabase
        from greymatter_core.sync.tracking import get_node_id
        from greymatter_core.schema.migrations import apply_all

        db = SQLiteDatabase(str(tmp_path / "sync.db"))
        apply_all(db)
        nid = get_node_id(db)
        assert len(nid) > 0
        assert nid != "unknown"

    def test_sync_version_starts_at_zero(self, tmp_path):
        from greymatter_core.db import SQLiteDatabase
        from greymatter_core.sync.tracking import get_sync_version
        from greymatter_core.schema.migrations import apply_all

        db = SQLiteDatabase(str(tmp_path / "sync.db"))
        apply_all(db)
        version = get_sync_version(db)
        assert version == 0
