"""Integration tests: context engine cross-module workflows.

Tests the context engine's integration with standards, documents, and patterns:
- Standards → context payload assembly
- Documents → token-budgeted retrieval
- Pattern confidence escalation → session context inclusion
- Context request tracking and relevance scoring feedback loop
- Token budget allocation under pressure
- Multi-project context scoping
"""

import json

import pytest

from greymatter_core.context import ContextEngine
from greymatter_core.db import SQLiteDatabase
from greymatter_core.documents import DocumentStore
from greymatter_core.patterns import PatternManager
from greymatter_core.sessions import SessionManager
from greymatter_core.standards import StandardsManager


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------

@pytest.fixture
def db():
    """Shared in-memory database."""
    database = SQLiteDatabase(":memory:")
    yield database
    database.close()


@pytest.fixture
def standards(db):
    return StandardsManager(db)


@pytest.fixture
def doc_store(db):
    return DocumentStore(db)


@pytest.fixture
def patterns(db):
    return PatternManager(db)


@pytest.fixture
def sessions(db):
    return SessionManager(db)


@pytest.fixture
def context_engine(db, doc_store, standards, patterns):
    return ContextEngine(db, doc_store, standards, patterns)


# ---------------------------------------------------------------------------
# 1. Standards → Context Payload
# ---------------------------------------------------------------------------

class TestStandardsContextPayload:
    """Tests standards appearing in context engine output."""

    def test_seeded_standards_in_context(self, standards, context_engine):
        """Seeded standards appear in the context payload as JSON."""
        count = standards.seed_defaults()
        assert count > 0

        ctx = context_engine.build_session_context(max_tokens=50000)
        payload = json.loads(ctx["quality_standards"])
        assert len(payload) > 0

        # Every entry has the expected keys
        for entry in payload:
            assert "category" in entry
            assert "rule" in entry
            assert "severity" in entry

    def test_deactivated_standards_excluded(self, standards, context_engine):
        """Deactivating a standard removes it from the context payload."""
        standards.seed_defaults()

        active = standards.get_active(severity_filter=["critical"])
        std_id = active[0]["id"]
        removed_rule = active[0]["rule"]

        standards.deactivate(std_id)

        ctx = context_engine.build_session_context()
        payload = json.loads(ctx["quality_standards"])
        rules = {s["rule"] for s in payload}
        assert removed_rule not in rules

    def test_empty_standards_produce_empty_payload(self, context_engine):
        """Without seeded standards, quality_standards is an empty JSON array."""
        ctx = context_engine.build_session_context()
        payload = json.loads(ctx["quality_standards"])
        assert payload == []


# ---------------------------------------------------------------------------
# 2. Documents → Token-Budgeted Retrieval
# ---------------------------------------------------------------------------

class TestDocumentContextRetrieval:
    """Tests document inclusion in context under token budget constraints."""

    def test_documents_included_in_context(self, doc_store, context_engine):
        """Created documents appear in context recent_context."""
        doc_store.create(
            doc_type="design",
            title="Architecture Decision: SQLite",
            content="We chose SQLite for local persistence." * 5,
            project="greymatter",
        )

        ctx = context_engine.build_session_context(
            project="greymatter", max_tokens=50000
        )
        assert len(ctx["recent_context"]) >= 1
        titles = [d["title"] for d in ctx["recent_context"]]
        assert "Architecture Decision: SQLite" in titles

    def test_token_budget_limits_documents(self, doc_store, context_engine, standards):
        """Documents exceeding the token budget are excluded or summarized."""
        standards.seed_defaults()

        # Create a large document that will consume most of the budget
        large_content = "This is a very detailed document. " * 500
        doc_store.create(
            doc_type="research",
            title="Large Research Doc",
            content=large_content,
            project="greymatter",
        )

        # Create a small document
        doc_store.create(
            doc_type="design",
            title="Small Design Doc",
            content="Short content here.",
            project="greymatter",
        )

        # Use a tight token budget
        ctx = context_engine.build_session_context(
            project="greymatter", max_tokens=500
        )
        # With tight budget, we may get fewer docs
        total_tokens = sum(
            d.get("token_estimate", 0) or 0 for d in ctx["recent_context"]
        )
        # Token budget is allocated after standards — shouldn't wildly exceed
        assert total_tokens <= 500

    def test_project_scoping_filters_documents(self, doc_store, context_engine):
        """Documents are filtered by project when project is specified."""
        doc_store.create(
            doc_type="design",
            title="GM Doc",
            content="GreyMatter architecture",
            project="greymatter",
        )
        doc_store.create(
            doc_type="design",
            title="SalesOS Doc",
            content="SalesOS pipeline design",
            project="salesos",
        )

        ctx_gm = context_engine.build_session_context(
            project="greymatter", max_tokens=50000
        )
        ctx_sales = context_engine.build_session_context(
            project="salesos", max_tokens=50000
        )

        gm_titles = {d["title"] for d in ctx_gm["recent_context"]}
        sales_titles = {d["title"] for d in ctx_sales["recent_context"]}

        assert "GM Doc" in gm_titles
        assert "SalesOS Doc" not in gm_titles
        assert "SalesOS Doc" in sales_titles
        assert "GM Doc" not in sales_titles


# ---------------------------------------------------------------------------
# 3. Pattern Confidence → Session Context
# ---------------------------------------------------------------------------

class TestPatternContextInclusion:
    """Tests that patterns appear in context only after confidence escalation."""

    def test_low_confidence_pattern_excluded(self, patterns, context_engine):
        """A freshly created (low confidence) pattern is not in session context."""
        pat = patterns.create(
            type="process",
            domain="testing",
            title="TDD First",
            observation="Write tests before code",
        )

        ctx = context_engine.build_session_context()
        pat_ids = [p["id"] for p in ctx["active_patterns"]]
        assert pat["id"] not in pat_ids

    def test_medium_confidence_pattern_included(self, patterns, sessions, context_engine):
        """A pattern with 3+ confirmations (medium confidence) appears in context."""
        pat = patterns.create(
            type="process",
            domain="testing",
            title="TDD First",
            observation="Write tests before code",
        )

        # Add 3 confirmations to reach medium confidence
        for _ in range(3):
            session = sessions.start(project="greymatter")
            patterns.add_evidence(pat["id"], session_id=session["id"])
            sessions.end(session["id"])

        # Verify confidence escalated
        updated = patterns.get(pat["id"])
        assert updated["confidence"] == "medium"

        ctx = context_engine.build_session_context()
        pat_ids = [p["id"] for p in ctx["active_patterns"]]
        assert pat["id"] in pat_ids

    def test_high_confidence_patterns_ranked_first(self, patterns, sessions, context_engine):
        """High confidence patterns appear before medium confidence ones."""
        med_pat = patterns.create(
            type="process", domain="testing",
            title="Medium Pattern", observation="Medium confidence obs",
        )
        high_pat = patterns.create(
            type="process", domain="testing",
            title="High Pattern", observation="High confidence obs",
        )

        # Escalate medium to medium (3 confirmations)
        for _ in range(3):
            s = sessions.start(project="greymatter")
            patterns.add_evidence(med_pat["id"], session_id=s["id"])
            sessions.end(s["id"])

        # Escalate high to high (7 confirmations)
        for _ in range(7):
            s = sessions.start(project="greymatter")
            patterns.add_evidence(high_pat["id"], session_id=s["id"])
            sessions.end(s["id"])

        ctx = context_engine.build_session_context()
        active = ctx["active_patterns"]
        pat_ids = [p["id"] for p in active]

        assert high_pat["id"] in pat_ids
        assert med_pat["id"] in pat_ids

        # High confidence should be first
        high_idx = pat_ids.index(high_pat["id"])
        med_idx = pat_ids.index(med_pat["id"])
        assert high_idx < med_idx


# ---------------------------------------------------------------------------
# 4. Context Request Tracking and Relevance Feedback
# ---------------------------------------------------------------------------

class TestContextRequestTracking:
    """Tests the context request recording and relevance scoring loop."""

    def test_record_and_retrieve_request(self, db, context_engine):
        """A recorded context request is persisted in the database."""
        context_engine.record_request(
            session_id="sess-001",
            query="How does auth work?",
            doc_ids=["doc-1", "doc-2"],
            tokens=450,
        )

        row = db.execute_one(
            "SELECT * FROM context_requests WHERE session_id = ?",
            ("sess-001",),
        )
        assert row is not None
        assert row["query"] == "How does auth work?"
        assert json.loads(row["documents_served"]) == ["doc-1", "doc-2"]
        assert row["tokens_served"] == 450
        assert row["relevance_score"] is None

    def test_evaluate_relevance_updates_score(self, db, context_engine):
        """Evaluating relevance updates the score for a context request."""
        context_engine.record_request(
            session_id="sess-002",
            query="Rate limiting patterns",
            doc_ids=["doc-3"],
            tokens=200,
        )

        row = db.execute_one(
            "SELECT id FROM context_requests WHERE session_id = ?",
            ("sess-002",),
        )
        request_id = row["id"]

        context_engine.evaluate_relevance(request_id, 0.85)

        updated = db.execute_one(
            "SELECT * FROM context_requests WHERE id = ?",
            (request_id,),
        )
        assert updated["relevance_score"] == pytest.approx(0.85)

    def test_multiple_requests_tracked_independently(self, db, context_engine):
        """Multiple context requests for the same session are tracked separately."""
        context_engine.record_request(
            session_id="sess-003", query="Query A", doc_ids=["doc-1"], tokens=100,
        )
        context_engine.record_request(
            session_id="sess-003", query="Query B", doc_ids=["doc-2"], tokens=200,
        )

        rows = db.execute(
            "SELECT * FROM context_requests WHERE session_id = ? ORDER BY id",
            ("sess-003",),
        )
        assert len(rows) == 2
        assert rows[0]["query"] == "Query A"
        assert rows[1]["query"] == "Query B"

    def test_relevance_feedback_loop(self, db, doc_store, context_engine):
        """Full cycle: build context → record request → score relevance."""
        # Create a document
        doc = doc_store.create(
            doc_type="design",
            title="Auth Design",
            content="OAuth2 implementation notes",
            project="greymatter",
        )

        # Build context (simulates an agent session)
        ctx = context_engine.build_session_context(
            project="greymatter", max_tokens=50000,
        )
        served_ids = [d["id"] for d in ctx["recent_context"]]
        assert doc["id"] in served_ids

        # Record the request
        context_engine.record_request(
            session_id="sess-feedback",
            query="auth implementation",
            doc_ids=served_ids,
            tokens=len(str(ctx["recent_context"])) // 4,
        )

        # Score the relevance
        row = db.execute_one(
            "SELECT id FROM context_requests WHERE session_id = ?",
            ("sess-feedback",),
        )
        context_engine.evaluate_relevance(row["id"], 0.92)

        scored = db.execute_one(
            "SELECT * FROM context_requests WHERE id = ?",
            (row["id"],),
        )
        assert scored["relevance_score"] == pytest.approx(0.92)


# ---------------------------------------------------------------------------
# 5. Combined Context: Standards + Documents + Patterns
# ---------------------------------------------------------------------------

class TestCombinedContext:
    """Tests the full context assembly with all three sources."""

    def test_all_three_sources_present(
        self, standards, doc_store, patterns, sessions, context_engine,
    ):
        """Context includes standards, documents, and escalated patterns."""
        # Seed standards
        standards.seed_defaults()

        # Create a document
        doc_store.create(
            doc_type="design",
            title="System Design",
            content="Microservices architecture overview",
            project="greymatter",
        )

        # Create and escalate a pattern to medium
        pat = patterns.create(
            type="architecture", domain="design",
            title="Service boundaries", observation="Each service owns its data",
        )
        for _ in range(3):
            s = sessions.start(project="greymatter")
            patterns.add_evidence(pat["id"], session_id=s["id"])
            sessions.end(s["id"])

        ctx = context_engine.build_session_context(
            project="greymatter", max_tokens=50000,
        )

        # All three sections present and non-empty
        assert json.loads(ctx["quality_standards"])
        assert ctx["recent_context"]
        assert ctx["active_patterns"]

    def test_context_keys_always_present(self, context_engine):
        """Context dict always has the three expected keys, even when empty."""
        ctx = context_engine.build_session_context()
        assert "quality_standards" in ctx
        assert "recent_context" in ctx
        assert "active_patterns" in ctx
