"""Tests validating FSRS review API documentation accuracy.

Ensures docs/fsrs-review-api.md stays in sync with the actual
ProactiveRecall implementation, MCP tool actions, REST endpoints,
constants, and test coverage references.
"""
from __future__ import annotations

import re
from pathlib import Path

import pytest

PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
DOCS_PATH = PROJECT_ROOT / "docs" / "fsrs-review-api.md"
RECALL_PATH = PROJECT_ROOT / "core" / "src" / "greymatter_core" / "recall.py"
SERVER_PATH = PROJECT_ROOT / "plugin" / "src" / "greymatter_plugin" / "server.py"
COORDINATOR_APP_PATH = PROJECT_ROOT / "coordinator" / "app.py"
UNIT_TESTS_PATH = PROJECT_ROOT / "core" / "tests" / "test_recall.py"
INTEGRATION_TESTS_PATH = (
    PROJECT_ROOT / "core" / "tests" / "integration" / "test_fsrs_review_api_integration.py"
)
COGNITIVE_SCHEMA_PATH = (
    PROJECT_ROOT / "core" / "src" / "greymatter_core" / "schema" / "cognitive.py"
)
COGNITIVE_MEMORY_PATH = (
    PROJECT_ROOT / "cognitive" / "src" / "cognitive" / "memory.py"
)


@pytest.fixture
def doc_content() -> str:
    """Load the FSRS review API documentation."""
    assert DOCS_PATH.exists(), f"Documentation file missing: {DOCS_PATH}"
    return DOCS_PATH.read_text()


@pytest.fixture
def recall_source() -> str:
    """Load the ProactiveRecall source code."""
    assert RECALL_PATH.exists(), f"ProactiveRecall source missing: {RECALL_PATH}"
    return RECALL_PATH.read_text()


@pytest.fixture
def server_source() -> str:
    """Load the MCP server source code."""
    assert SERVER_PATH.exists(), f"MCP server source missing: {SERVER_PATH}"
    return SERVER_PATH.read_text()


# =========================================================================
# Documentation and source files exist
# =========================================================================


class TestFilesExist:
    """Verify all files referenced by the documentation exist."""

    def test_docs_file_exists(self):
        assert DOCS_PATH.exists()

    def test_recall_source_exists(self):
        assert RECALL_PATH.exists()

    def test_server_source_exists(self):
        assert SERVER_PATH.exists()

    def test_coordinator_app_exists(self):
        assert COORDINATOR_APP_PATH.exists()

    def test_unit_tests_exist(self):
        assert UNIT_TESTS_PATH.exists()

    def test_integration_tests_exist(self):
        assert INTEGRATION_TESTS_PATH.exists()

    def test_cognitive_memory_exists(self):
        assert COGNITIVE_MEMORY_PATH.exists()


# =========================================================================
# FSRS-6 constants match between code and docs
# =========================================================================


class TestConstantsDocumented:
    """FSRS-6 constants in docs match the actual values in recall.py."""

    def test_c_floor_value(self, doc_content, recall_source):
        from greymatter_core.recall import C_FLOOR

        assert str(C_FLOOR) in doc_content
        assert "C_FLOOR" in doc_content

    def test_review_threshold_value(self, doc_content, recall_source):
        from greymatter_core.recall import REVIEW_THRESHOLD

        assert str(REVIEW_THRESHOLD) in doc_content
        assert "REVIEW_THRESHOLD" in doc_content

    def test_confirm_factor_value(self, doc_content, recall_source):
        from greymatter_core.recall import CONFIRM_FACTOR

        assert str(CONFIRM_FACTOR) in doc_content
        assert "CONFIRM_FACTOR" in doc_content

    def test_max_stability_days_value(self, doc_content, recall_source):
        from greymatter_core.recall import MAX_STABILITY_DAYS

        assert str(MAX_STABILITY_DAYS) in doc_content
        assert "MAX_STABILITY_DAYS" in doc_content

    def test_default_stability_value(self, doc_content, recall_source):
        from greymatter_core.recall import DEFAULT_STABILITY

        assert str(DEFAULT_STABILITY) in doc_content
        assert "DEFAULT_STABILITY" in doc_content

    def test_min_confidence_for_review_value(self, doc_content, recall_source):
        from greymatter_core.recall import MIN_CONFIDENCE_FOR_REVIEW

        assert str(MIN_CONFIDENCE_FOR_REVIEW) in doc_content
        assert "MIN_CONFIDENCE_FOR_REVIEW" in doc_content

    def test_all_six_constants_in_table(self, doc_content):
        """The docs should list all 6 constants in the constants table."""
        constant_names = [
            "C_FLOOR",
            "REVIEW_THRESHOLD",
            "CONFIRM_FACTOR",
            "MAX_STABILITY_DAYS",
            "DEFAULT_STABILITY",
            "MIN_CONFIDENCE_FOR_REVIEW",
        ]
        for name in constant_names:
            assert name in doc_content, (
                f"Constant '{name}' not found in documentation"
            )


# =========================================================================
# FSRS-6 formula documented accurately
# =========================================================================


class TestFormulaDocumented:
    """The FSRS-6 power-law formula is correctly documented."""

    def test_formula_present(self, doc_content):
        """Docs contain the FSRS-6 formula."""
        assert "(1 + t / (2" in doc_content
        assert "(-2)" in doc_content or "^(-2)" in doc_content

    def test_known_value_documented(self, doc_content):
        """The hand-computed known value R(90, 45) = 0.2875 is in the docs."""
        assert "0.2875" in doc_content

    def test_formula_matches_code(self, recall_source):
        """Code implements the power-law formula with exponent -2."""
        assert "(1 + days_elapsed / (2 * stability)) ** (-2)" in recall_source


# =========================================================================
# ProactiveRecall public API documented
# =========================================================================


class TestProactiveRecallAPIDocumented:
    """All public methods of ProactiveRecall are documented."""

    def test_get_due_for_review_documented(self, doc_content):
        assert "get_due_for_review" in doc_content

    def test_record_review_documented(self, doc_content):
        assert "record_review" in doc_content

    def test_format_recall_prompt_documented(self, doc_content):
        assert "format_recall_prompt" in doc_content

    def test_get_due_count_documented(self, doc_content):
        assert "get_due_count" in doc_content

    def test_batch_promote_high_confidence_documented(self, doc_content):
        assert "batch_promote_high_confidence" in doc_content

    def test_record_review_params_documented(self, doc_content):
        """record_review parameter names appear in docs."""
        for param in ("entry_id", "source_table", "stability_factor", "review_source"):
            assert param in doc_content, (
                f"record_review param '{param}' not in documentation"
            )

    def test_get_due_return_keys_documented(self, doc_content):
        """Return dict keys from get_due_for_review are documented."""
        for key in (
            "source_table", "entry_type", "title", "domain",
            "content_preview", "retrievability", "days_since_review", "stability",
        ):
            assert key in doc_content, (
                f"get_due_for_review return key '{key}' not in documentation"
            )


# =========================================================================
# MCP tool actions documented
# =========================================================================


class TestMCPActionsDocumented:
    """All review-related gm_observe actions are documented."""

    def test_due_action_documented(self, doc_content):
        assert "due" in doc_content
        assert "Items Approaching Decay" in doc_content

    def test_review_action_documented(self, doc_content):
        assert "Record Single Review" in doc_content

    def test_batch_review_action_documented(self, doc_content):
        assert "batch_review" in doc_content
        assert "Record Multiple Reviews" in doc_content

    def test_maintain_action_documented(self, doc_content):
        assert "maintain" in doc_content
        assert "Run Decay Pass" in doc_content

    def test_consolidate_action_documented(self, doc_content):
        assert "consolidate" in doc_content
        assert "Decay + Promotion" in doc_content

    def test_auto_promote_action_documented(self, doc_content):
        assert "auto_promote" in doc_content
        assert "Manual Promotion Trigger" in doc_content

    def test_observation_id_param_documented(self, doc_content):
        assert "observation_id" in doc_content

    def test_entry_ids_param_documented(self, doc_content):
        assert "entry_ids" in doc_content

    def test_review_source_param_documented(self, doc_content):
        assert "review_source" in doc_content

    def test_stability_factor_param_documented(self, doc_content):
        assert "stability_factor" in doc_content


# =========================================================================
# REST API endpoints documented
# =========================================================================


class TestRESTEndpointsDocumented:
    """REST API review endpoints match docs."""

    def test_post_review_work_id_documented(self, doc_content):
        assert "POST /api/review/{work_id}" in doc_content

    def test_post_work_review_documented(self, doc_content):
        assert "POST /api/work/{work_id}/review" in doc_content

    def test_implementer_agent_field_documented(self, doc_content):
        assert "implementer_agent" in doc_content

    def test_review_type_field_documented(self, doc_content):
        assert "review_type" in doc_content

    def test_verdict_field_documented(self, doc_content):
        assert "verdict" in doc_content

    def test_verdict_values_documented(self, doc_content):
        assert "approved" in doc_content
        assert "needs_revision" in doc_content

    def test_feedback_field_documented(self, doc_content):
        assert "feedback" in doc_content

    def test_reviewer_agent_field_documented(self, doc_content):
        assert "reviewer_agent" in doc_content


# =========================================================================
# Stability by source table documented accurately
# =========================================================================


class TestStabilityFormulasDocumented:
    """Stability formulas per source table match the code."""

    def test_knowledge_stability_formula(self, doc_content, recall_source):
        """Knowledge entries: 30 + (applied_count * 15)."""
        assert "30 + (applied_count" in doc_content or "30 + (applied_count × 15)" in doc_content
        assert "30 + (applied * 15)" in recall_source

    def test_observations_stability_formula(self, doc_content, recall_source):
        """Observations: stability_days * factor."""
        assert "stability_days" in doc_content
        assert "current_stability * factor" in recall_source

    def test_patterns_stability_formula(self, doc_content, recall_source):
        """Patterns: 60 + (confirmations * 20)."""
        assert "60 + (confirmations" in doc_content or "60 + (confirmations × 20)" in doc_content
        assert "60 + (confirmations * 20)" in recall_source


# =========================================================================
# Review lifecycle documented
# =========================================================================


class TestReviewLifecycleDocumented:
    """Active vs passive review and promotion pathways are documented."""

    def test_active_review_factor_documented(self, doc_content):
        assert "2.5" in doc_content
        assert "Active" in doc_content

    def test_passive_review_factor_documented(self, doc_content):
        assert "1.5" in doc_content
        assert "Passive" in doc_content

    def test_direct_promotion_threshold_documented(self, doc_content):
        """Direct promotion: confidence > 0.8, review_count >= 3."""
        assert "0.8" in doc_content
        assert ">= 3" in doc_content or "review_count >= 3" in doc_content

    def test_cluster_promotion_threshold_documented(self, doc_content):
        """Cluster: 3+ observations from 2+ sessions."""
        assert "3+" in doc_content or "3 or more" in doc_content
        assert "2+" in doc_content or "2 or more" in doc_content

    def test_jaccard_threshold_documented(self, doc_content):
        """Jaccard similarity threshold 0.4 is documented."""
        assert "0.4" in doc_content

    def test_domain_similarity_boost_documented(self, doc_content):
        """Domain overlap +0.2 boost is documented."""
        assert "0.2" in doc_content


# =========================================================================
# ID prefix auto-detection documented
# =========================================================================


class TestIDPrefixDocumented:
    """ID prefix -> source table mapping is documented."""

    def test_kn_prefix_documented(self, doc_content):
        assert "kn-" in doc_content

    def test_ke_prefix_documented(self, doc_content):
        assert "ke-" in doc_content

    def test_obs_prefix_documented(self, doc_content):
        assert "obs-" in doc_content

    def test_pat_prefix_documented(self, doc_content):
        assert "pat-" in doc_content


# =========================================================================
# Database schema fields documented
# =========================================================================


class TestSchemaFieldsDocumented:
    """Spaced repetition schema fields are documented."""

    def test_observations_stability_days(self, doc_content):
        assert "stability_days" in doc_content

    def test_observations_last_confirmed_at(self, doc_content):
        assert "last_confirmed_at" in doc_content

    def test_observations_review_count(self, doc_content):
        assert "review_count" in doc_content

    def test_observations_last_reviewed_at(self, doc_content):
        assert "last_reviewed_at" in doc_content

    def test_observations_review_source(self, doc_content):
        assert "review_source" in doc_content

    def test_observations_confidence(self, doc_content):
        assert "confidence" in doc_content

    def test_observations_knowledge_id(self, doc_content):
        assert "knowledge_id" in doc_content

    def test_observations_promoted_at(self, doc_content):
        assert "promoted_at" in doc_content

    def test_knowledge_applied_count(self, doc_content):
        assert "applied_count" in doc_content

    def test_patterns_confirmations(self, doc_content):
        assert "confirmations" in doc_content

    def test_patterns_last_applied_at(self, doc_content):
        assert "last_applied_at" in doc_content


# =========================================================================
# Urgency levels documented
# =========================================================================


class TestUrgencyLevelsDocumented:
    """format_recall_prompt urgency labels are documented."""

    def test_critical_level_documented(self, doc_content):
        assert "CRITICAL" in doc_content

    def test_high_level_documented(self, doc_content):
        assert "HIGH" in doc_content

    def test_due_level_documented(self, doc_content):
        assert "DUE" in doc_content

    def test_urgency_thresholds_documented(self, doc_content):
        """Thresholds: < 0.3 -> CRITICAL, < 0.5 -> HIGH, >= 0.5 -> DUE."""
        assert "0.3" in doc_content
        assert "0.5" in doc_content


# =========================================================================
# Unit test classes documented
# =========================================================================


class TestUnitTestClassesDocumented:
    """All unit test classes from test_recall.py are referenced in docs."""

    def test_unit_test_classes_in_docs(self, doc_content):
        unit_source = UNIT_TESTS_PATH.read_text()
        class_names = re.findall(r"^class (Test\w+)", unit_source, re.MULTILINE)
        assert len(class_names) > 0, "No test classes found in unit tests"
        for cls in class_names:
            assert cls in doc_content, (
                f"Unit test class '{cls}' not found in documentation"
            )


# =========================================================================
# Integration test classes documented
# =========================================================================


class TestIntegrationTestClassesDocumented:
    """All integration test classes are referenced in docs."""

    def test_integration_test_classes_in_docs(self, doc_content):
        integration_source = INTEGRATION_TESTS_PATH.read_text()
        class_names = re.findall(
            r"^class (Test\w+)", integration_source, re.MULTILINE
        )
        assert len(class_names) > 0, "No test classes found in integration tests"
        for cls in class_names:
            assert cls in doc_content, (
                f"Integration test class '{cls}' not found in documentation"
            )


# =========================================================================
# Configuration section documented
# =========================================================================


class TestConfigurationDocumented:
    """Configuration environment variables are documented."""

    def test_gm_db_path_documented(self, doc_content, recall_source):
        assert "GM_DB_PATH" in recall_source
        assert "GM_DB_PATH" in doc_content

    def test_default_db_path_documented(self, doc_content):
        assert "greymatter.db" in doc_content


# =========================================================================
# References section exists
# =========================================================================


class TestReferencesDocumented:
    """Academic references are included."""

    def test_ebbinghaus_cited(self, doc_content):
        assert "Ebbinghaus" in doc_content

    def test_wozniak_cited(self, doc_content):
        assert "Wozniak" in doc_content

    def test_fsrs_cited(self, doc_content):
        assert "Ye" in doc_content or "FSRS" in doc_content

    def test_cognitive_architecture_cross_referenced(self, doc_content):
        assert "cognitive-memory-architecture.md" in doc_content


# =========================================================================
# Categorize action documented
# =========================================================================


class TestCategorizeActionDocumented:
    """The categorize action in gm_observe is documented."""

    def test_categorize_action_heading(self, doc_content):
        assert "categorize" in doc_content
        assert "Auto-Categorize Observations" in doc_content

    def test_categorize_single_response(self, doc_content):
        assert '"categorized"' in doc_content

    def test_categorize_batch_response(self, doc_content):
        assert "batch_complete" in doc_content

    def test_categorize_dependency_error(self, doc_content):
        assert "greymatter_core.categorizer not available" in doc_content

    def test_categorize_nemotron_mentioned(self, doc_content):
        assert "Nemotron" in doc_content

    def test_categorize_action_in_server(self, server_source):
        """Server.py actually implements the categorize action."""
        assert 'action == "categorize"' in server_source


# =========================================================================
# Cognitive memory bridge documented
# =========================================================================


class TestCognitiveMemoryBridgeDocumented:
    """The cognitive/memory.py MemorySystem bridge is documented."""

    def test_memory_system_documented(self, doc_content):
        assert "MemorySystem" in doc_content

    def test_cognitive_memory_path_documented(self, doc_content):
        assert "cognitive/src/cognitive/memory.py" in doc_content

    def test_interference_penalty_documented(self, doc_content):
        assert "INTERFERENCE_PENALTY" in doc_content

    def test_free_tier_limits_documented(self, doc_content):
        assert "FREE_OBSERVATION_LIMIT" in doc_content

    def test_shared_constants_listed(self, doc_content):
        """Shared constants between recall.py and memory.py are listed."""
        for const in ("C_FLOOR", "CONFIRM_FACTOR", "MAX_STABILITY_DAYS", "DEFAULT_STABILITY"):
            assert const in doc_content

    def test_memory_system_exists_in_source(self):
        """Verify MemorySystem class exists in cognitive/memory.py."""
        source = COGNITIVE_MEMORY_PATH.read_text()
        assert "class MemorySystem" in source


# =========================================================================
# Initial stability by category documented
# =========================================================================


class TestInitialStabilityDocumented:
    """Category-specific initial stability values are documented."""

    def test_initial_stability_section_exists(self, doc_content):
        assert "Initial Stability by Category" in doc_content

    def test_category_stability_values(self, doc_content):
        """All category stability values from cognitive/memory.py are documented."""
        assert "120 days" in doc_content  # decision
        assert "90 days" in doc_content   # correction, gotcha
        assert "60 days" in doc_content   # pattern
        assert "30 days" in doc_content   # reference

    def test_initial_confidence_values(self, doc_content):
        """Category initial confidence values are documented."""
        for val in ("0.7", "0.6", "0.55", "0.5"):
            assert val in doc_content

    def test_all_categories_listed(self, doc_content):
        """All valid categories are in the initial stability table."""
        for cat in ("decision", "correction", "gotcha", "pattern", "insight", "reference"):
            assert cat in doc_content

    def test_category_values_match_source(self):
        """Initial stability values in docs match cognitive/memory.py source."""
        source = COGNITIVE_MEMORY_PATH.read_text()
        assert '"correction": 90' in source
        assert '"pattern": 60' in source
        assert '"decision": 120' in source
        assert '"reference": 30' in source


# =========================================================================
# Error handling documented
# =========================================================================


class TestErrorHandlingDocumented:
    """Error handling section covers common failure scenarios."""

    def test_error_handling_section_exists(self, doc_content):
        assert "Error Handling" in doc_content

    def test_mcp_error_format(self, doc_content):
        assert '"error"' in doc_content

    def test_rate_limit_error_mentioned(self, doc_content):
        assert "Rate limit" in doc_content or "rate limit" in doc_content

    def test_rest_error_codes_documented(self, doc_content):
        assert "400" in doc_content
        assert "404" in doc_content

    def test_proactive_recall_graceful_failures(self, doc_content):
        """Documents that ProactiveRecall returns safe defaults on failure."""
        assert "returns `[]`" in doc_content
        assert "returns `False`" in doc_content


# =========================================================================
# Troubleshooting section documented
# =========================================================================


class TestTroubleshootingDocumented:
    """Troubleshooting section covers common operational issues."""

    def test_troubleshooting_section_exists(self, doc_content):
        assert "Troubleshooting" in doc_content

    def test_no_due_items_troubleshooting(self, doc_content):
        assert "No items showing as due for review" in doc_content

    def test_promotion_troubleshooting(self, doc_content):
        assert "Observations not promoting to knowledge" in doc_content

    def test_stability_troubleshooting(self, doc_content):
        assert "Reviews not increasing stability" in doc_content

    def test_database_connection_troubleshooting(self, doc_content):
        assert "Database connection issues" in doc_content


# =========================================================================
# Source file references accurate
# =========================================================================


class TestSourceReferencesAccurate:
    """File path and line references in docs point to real locations."""

    def test_recall_py_referenced(self, doc_content):
        assert "recall.py" in doc_content

    def test_server_py_referenced(self, doc_content):
        assert "server.py" in doc_content

    def test_coordinator_app_referenced(self, doc_content):
        assert "coordinator/app.py" in doc_content

    def test_cognitive_py_referenced(self, doc_content):
        assert "cognitive.py" in doc_content

    def test_cognitive_memory_py_referenced(self, doc_content):
        assert "cognitive/src/cognitive/memory.py" in doc_content
