"""Tests validating rate limiter documentation accuracy.

Ensures docs/rate-limiter.md stays in sync with the actual rate limiter
implementations across the MCP server, email subsystem, and dispatch daemon.
"""
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" / "rate-limiter.md"
SERVER_PATH = PROJECT_ROOT / "plugin" / "src" / "greymatter_plugin" / "server.py"
EMAIL_PATH = PROJECT_ROOT / "plugin" / "src" / "greymatter_plugin" / "email.py"
DAEMON_PATH = PROJECT_ROOT / "dispatch" / "daemon.py"
CONFIG_PATH = PROJECT_ROOT / "dispatch" / "config.py"
NOTIFICATIONS_PATH = PROJECT_ROOT / "dispatch" / "notifications.py"
COORDINATOR_STATE_PATH = PROJECT_ROOT / "coordinator" / "state.py"
COORDINATOR_APP_PATH = PROJECT_ROOT / "coordinator" / "app.py"
DISPATCH_TEST_PATH = PROJECT_ROOT / "dispatch" / "tests" / "test_rate_limit.py"
PLUGIN_TEST_PATH = PROJECT_ROOT / "plugin" / "tests" / "test_rate_limiter_integration.py"


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


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


@pytest.fixture
def email_source() -> str:
    """Load the email module source code."""
    assert EMAIL_PATH.exists(), f"Email source missing: {EMAIL_PATH}"
    return EMAIL_PATH.read_text()


@pytest.fixture
def daemon_source() -> str:
    """Load the dispatch daemon source code."""
    assert DAEMON_PATH.exists(), f"Daemon source missing: {DAEMON_PATH}"
    return DAEMON_PATH.read_text()


@pytest.fixture
def config_source() -> str:
    """Load the dispatch config source code."""
    assert CONFIG_PATH.exists(), f"Config source missing: {CONFIG_PATH}"
    return CONFIG_PATH.read_text()


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


class TestFilesExist:
    """Verify all documented source files exist."""

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

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

    def test_email_source_exists(self):
        assert EMAIL_PATH.exists()

    def test_daemon_source_exists(self):
        assert DAEMON_PATH.exists()

    def test_config_source_exists(self):
        assert CONFIG_PATH.exists()

    def test_notifications_source_exists(self):
        assert NOTIFICATIONS_PATH.exists()

    def test_coordinator_state_exists(self):
        assert COORDINATOR_STATE_PATH.exists()

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

    def test_dispatch_tests_exist(self):
        assert DISPATCH_TEST_PATH.exists()

    def test_plugin_integration_tests_exist(self):
        assert PLUGIN_TEST_PATH.exists()


# =========================================================================
# MCP server rate limiter constants match docs
# =========================================================================


class TestMcpServerConstants:
    """Documented MCP rate limiter values match the source code."""

    def test_rate_limit_value(self, doc_content: str, server_source: str):
        """Docs say 60 calls/tool/window — matches _RATE_LIMIT in server.py."""
        match = re.search(r"_RATE_LIMIT\s*=\s*(\d+)", server_source)
        assert match, "_RATE_LIMIT not found in server.py"
        actual = match.group(1)
        assert actual in doc_content, (
            f"_RATE_LIMIT={actual} not documented (docs may say a different value)"
        )

    def test_rate_window_value(self, doc_content: str, server_source: str):
        """Docs say 60s window — matches _RATE_WINDOW in server.py."""
        match = re.search(r"_RATE_WINDOW\s*=\s*([\d.]+)", server_source)
        assert match, "_RATE_WINDOW not found in server.py"
        actual = match.group(1).rstrip("0").rstrip(".")
        assert actual in doc_content, (
            f"_RATE_WINDOW={actual} not documented"
        )

    def test_check_rate_limit_function_exists(self, server_source: str):
        """_check_rate_limit function exists in server.py as documented."""
        assert "_check_rate_limit" in server_source

    def test_rate_limit_exceeded_event_type(self, doc_content: str, server_source: str):
        """Docs mention rate_limit_exceeded event — matches server.py."""
        assert "rate_limit_exceeded" in server_source
        assert "rate_limit_exceeded" in doc_content


# =========================================================================
# Protected operations documented accurately
# =========================================================================


class TestProtectedOperationsDocumented:
    """Documented rate-limited tools and actions match the code."""

    def test_gm_pattern_rate_limited_actions(self, doc_content: str, server_source: str):
        """gm_pattern's documented rate-limited actions match code."""
        # Find the rate-limited actions for gm_pattern in code
        pattern = re.search(
            r'action\s+in\s+\([^)]*\).*?_check_rate_limit\("gm_pattern"\)',
            server_source, re.DOTALL,
        )
        if not pattern:
            # Alternative pattern: check by looking at the action guard
            assert '_check_rate_limit("gm_pattern")' in server_source
        assert "gm_pattern" in doc_content

    def test_gm_knowledge_rate_limited(self, doc_content: str, server_source: str):
        """gm_knowledge rate limiting is documented."""
        assert '_check_rate_limit("gm_knowledge")' in server_source
        assert "gm_knowledge" in doc_content

    def test_gm_observe_rate_limited(self, doc_content: str, server_source: str):
        """gm_observe rate limiting is documented."""
        assert '_check_rate_limit("gm_observe")' in server_source
        assert "gm_observe" in doc_content


# =========================================================================
# Email rate limiter defaults match docs
# =========================================================================


class TestEmailRateLimiterDefaults:
    """Documented email rate limiter defaults match email.py."""

    def test_max_per_minute_default(self, doc_content: str, email_source: str):
        """Docs say 10 emails/minute — matches RateLimiter default."""
        match = re.search(r"max_per_minute:\s*int\s*=\s*(\d+)", email_source)
        assert match, "max_per_minute default not found in email.py"
        actual = match.group(1)
        assert actual == "10"
        assert "10" in doc_content

    def test_max_per_day_default(self, doc_content: str, email_source: str):
        """Docs say 90 emails/day — matches RateLimiter default."""
        match = re.search(r"max_per_day:\s*int\s*=\s*(\d+)", email_source)
        assert match, "max_per_day default not found in email.py"
        actual = match.group(1)
        assert actual == "90"
        assert "90" in doc_content

    def test_rate_limiter_class_exists(self, email_source: str):
        """RateLimiter class exists in email.py as documented."""
        assert "class RateLimiter" in email_source


# =========================================================================
# Dispatch daemon config env vars match docs
# =========================================================================


class TestDispatchConfigEnvVars:
    """Documented env vars match dispatch/config.py defaults."""

    @pytest.mark.parametrize(
        "env_var,expected_default",
        [
            ("GM_DISPATCH_RATE_LIMIT_MINUTES", "10"),
            ("GM_DISPATCH_ACTIVE_RATE_LIMIT", "3"),
            ("GM_DISPATCH_OFFHOURS_RATE_LIMIT", "1"),
            ("GM_DISPATCH_ACTIVE_MAX_CONCURRENT", "3"),
            ("GM_DISPATCH_OFFHOURS_MAX_CONCURRENT", "7"),
            ("GM_DISPATCH_ACTIVE_START", "8"),
            ("GM_DISPATCH_ACTIVE_END", "22"),
        ],
    )
    def test_env_var_documented(self, doc_content: str, config_source: str, env_var: str, expected_default: str):
        """Each dispatch env var appears in both docs and config with correct default."""
        assert env_var in config_source, f"{env_var} not found in config.py"
        assert env_var in doc_content, f"{env_var} not documented in rate-limiter.md"
        # Verify the default value appears near the env var in the config
        pattern = re.compile(rf'{env_var}[^)]*"({expected_default})"')
        assert pattern.search(config_source), (
            f"{env_var} default is not {expected_default} in config.py"
        )


# =========================================================================
# Dispatch daemon rate limit methods exist
# =========================================================================


class TestDispatchDaemonMethods:
    """Documented daemon methods exist in daemon.py."""

    def test_detect_rate_limit_exists(self, daemon_source: str):
        assert "_detect_rate_limit" in daemon_source

    def test_rate_limit_ok_exists(self, daemon_source: str):
        assert "_rate_limit_ok" in daemon_source

    def test_effective_rate_limit_exists(self, daemon_source: str):
        assert "_effective_rate_limit" in daemon_source

    def test_check_coordinator_rate_limit_exists(self, daemon_source: str):
        assert "_check_coordinator_rate_limit" in daemon_source

    def test_report_rate_limit_to_coordinator_exists(self, daemon_source: str):
        assert "_report_rate_limit_to_coordinator" in daemon_source


# =========================================================================
# Failed-agent-only detection is documented
# =========================================================================


class TestFailedAgentOnlyDetection:
    """Docs reflect that rate limit detection only runs on failed agents."""

    def test_retcode_guard_in_daemon(self, daemon_source: str):
        """Daemon only calls _detect_rate_limit when retcode != 0."""
        assert "retcode != 0" in daemon_source

    def test_docs_mention_failed_agents(self, doc_content: str):
        """Docs mention that detection only runs on failed agents."""
        assert "failed agent" in doc_content.lower() or "retcode" in doc_content.lower()


# =========================================================================
# Coordinator API endpoints documented
# =========================================================================


class TestCoordinatorApiDocumented:
    """Documented coordinator rate limit endpoints exist in code."""

    def test_rate_limit_endpoint_in_coordinator(self, doc_content: str):
        """POST/GET/DELETE /api/dispatch/rate-limit documented."""
        assert "POST /api/dispatch/rate-limit" in doc_content
        assert "GET /api/dispatch/rate-limit" in doc_content
        assert "DELETE /api/dispatch/rate-limit" in doc_content

    def test_rate_limit_endpoint_in_coordinator_app(self):
        """Coordinator app.py defines rate-limit routes."""
        source = COORDINATOR_APP_PATH.read_text()
        assert "rate-limit" in source or "rate_limit" in source

    def test_coordinator_state_methods(self):
        """CoordinatorState has rate limit methods."""
        source = COORDINATOR_STATE_PATH.read_text()
        assert "report_rate_limit" in source
        assert "get_rate_limit" in source
        assert "clear_rate_limit" in source


# =========================================================================
# Document structure — required sections present
# =========================================================================


class TestDocumentStructure:
    """Rate limiter docs have all required sections."""

    @pytest.mark.parametrize(
        "section",
        [
            "Architecture Overview",
            "MCP Server Rate Limiter",
            "Email Rate Limiter",
            "Dispatch Daemon Rate Limiter",
            "Coordinator Rate Limit API",
            "Schedule-Aware Configuration",
            "Configuration Reference",
            "Testing",
        ],
    )
    def test_section_exists(self, doc_content: str, section: str):
        assert section in doc_content, f"Missing section: {section}"

    def test_version_present(self, doc_content: str):
        """Docs include a version tag."""
        assert "Version" in doc_content

    def test_sources_listed(self, doc_content: str):
        """Docs list source file paths."""
        assert "server.py" in doc_content
        assert "daemon.py" in doc_content
        assert "email.py" in doc_content
        assert "config.py" in doc_content


# =========================================================================
# Notification env vars documented
# =========================================================================


class TestNotificationEnvVarsDocumented:
    """Documented notification env vars match code."""

    @pytest.mark.parametrize(
        "env_var",
        [
            "GM_TELEGRAM_BOT_TOKEN",
            "GM_TELEGRAM_CHAT_ID",
            "GM_NOTIFY_ENABLED",
            "GM_NOTIFY_WEBHOOK_URL",
        ],
    )
    def test_notification_env_var_documented(self, doc_content: str, env_var: str):
        assert env_var in doc_content, f"Notification env var {env_var} not documented"


# =========================================================================
# Proactive throttling thresholds documented
# =========================================================================


class TestProactiveThrottlingDocumented:
    """Documented utilization thresholds match daemon.py."""

    def test_90_percent_pause(self, doc_content: str, daemon_source: str):
        """90% utilization threshold documented and in code."""
        assert "0.90" in daemon_source or "90%" in daemon_source
        assert "90%" in doc_content

    def test_80_percent_throttle(self, doc_content: str, daemon_source: str):
        """80% utilization threshold documented and in code."""
        assert "0.80" in daemon_source
        assert "80%" in doc_content

    def test_60_percent_restore(self, doc_content: str, daemon_source: str):
        """60% utilization restore threshold documented and in code."""
        assert "0.60" in daemon_source
        assert "60%" in doc_content

    def test_30min_cap_documented(self, doc_content: str, daemon_source: str):
        """30-minute cap on pause duration documented and in code."""
        assert "1800" in daemon_source
        assert "30 min" in doc_content.lower() or "1800" in doc_content
