"""Verify SDK README examples don't drift from the actual API.

Extracts attribute access patterns (gw.X.Y, MCPGateway.X) from README code
blocks and checks they exist on the real classes. This catches renamed methods,
removed resources, and wrong parameter names before they reach PyPI.
"""

from __future__ import annotations

import ast
import re
from pathlib import Path

import pytest

from mcpgateway_sdk._client import MCPGateway
from mcpgateway_sdk._exceptions import (
    AuthError,
    ConflictError,
    ForbiddenError,
    GatewayError,
    NotFoundError,
    RateLimitError,
    ValidationError,
)

SDK_ROOT = Path(__file__).resolve().parent.parent.parent
README = SDK_ROOT / "README.md"

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

_CODE_BLOCK_RE = re.compile(r"```python\n(.*?)```", re.DOTALL)


def _extract_python_blocks(md_text: str) -> list[str]:
    """Return all Python code blocks from markdown text."""
    return _CODE_BLOCK_RE.findall(md_text)


def _all_blocks() -> list[str]:
    """Read README and return Python code blocks."""
    if not README.exists():
        pytest.skip("sdk/README.md not found")
    return _extract_python_blocks(README.read_text())


# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------


class TestReadmeExists:
    """README.md must exist and have content."""

    def test_readme_exists(self) -> None:
        assert README.exists(), "sdk/README.md is missing"

    def test_readme_not_empty(self) -> None:
        assert README.stat().st_size > 500, "sdk/README.md is suspiciously small"


class TestReadmeCodeBlocksSyntax:
    """All Python code blocks must be valid syntax."""

    @pytest.fixture(scope="class")
    def blocks(self) -> list[str]:
        return _all_blocks()

    def test_has_code_blocks(self, blocks: list[str]) -> None:
        assert len(blocks) >= 3, "README should have at least 3 Python examples"

    def test_all_blocks_parse(self, blocks: list[str]) -> None:
        for i, block in enumerate(blocks):
            try:
                ast.parse(block)
            except SyntaxError as exc:
                pytest.fail(f"Code block {i} has invalid syntax: {exc}")


class TestReadmeResourceAccess:
    """README examples reference real attributes on MCPGateway."""

    # The README uses `gw.X.Y(...)` patterns. These are the resource names
    # that must exist as attributes on MCPGateway.
    EXPECTED_RESOURCES = {"servers", "tools", "skills", "sessions", "sandboxes", "auth", "cache"}

    def test_all_resources_exist_on_client(self) -> None:
        """Every resource listed in README features section exists on MCPGateway."""
        # Resources are instance attributes set in __init__, so we inspect source
        import inspect

        source = inspect.getsource(MCPGateway.__init__)
        for resource in self.EXPECTED_RESOURCES:
            assert f"self.{resource}" in source, (
                f"README references gw.{resource} but MCPGateway.__init__ "
                f"doesn't assign self.{resource}"
            )

    def test_readme_method_references_exist(self) -> None:
        """Methods called in README (gw.X.Y) must exist on the resource class."""
        if not README.exists():
            pytest.skip("sdk/README.md not found")

        md_text = README.read_text()

        # Match patterns like: gw.servers.list, gw.tools.search, gw.sessions.create
        pattern = re.compile(r"gw\.(\w+)\.(\w+)")
        matches = pattern.findall(md_text)
        assert len(matches) > 0, "No gw.resource.method patterns found in README"

        # Build a dummy client to inspect resource classes
        # (MCPGateway.__init__ requires no args to fail, so we inspect the class)
        resource_classes: dict[str, type] = {}
        import mcpgateway_sdk.resources.auth as _auth
        import mcpgateway_sdk.resources.cache as _cache
        import mcpgateway_sdk.resources.sandboxes as _sandboxes
        import mcpgateway_sdk.resources.servers as _servers
        import mcpgateway_sdk.resources.sessions as _sessions
        import mcpgateway_sdk.resources.skills as _skills
        import mcpgateway_sdk.resources.tools as _tools

        resource_classes = {
            "servers": _servers.ServersResource,
            "tools": _tools.ToolsResource,
            "skills": _skills.SkillsResource,
            "sessions": _sessions.SessionsResource,
            "sandboxes": _sandboxes.SandboxesResource,
            "auth": _auth.AuthResource,
            "cache": _cache.CacheResource,
        }

        errors: list[str] = []
        for resource_name, method_name in matches:
            if resource_name not in resource_classes:
                errors.append(f"gw.{resource_name} is not a known resource")
                continue
            cls = resource_classes[resource_name]
            if not hasattr(cls, method_name):
                errors.append(
                    f"gw.{resource_name}.{method_name}() referenced in README "
                    f"but {cls.__name__} has no '{method_name}' method"
                )

        assert not errors, "README references non-existent methods:\n" + "\n".join(errors)


class TestReadmeExceptionImports:
    """README exception imports must match what __init__ exports."""

    EXPORTED_EXCEPTIONS = {
        "GatewayError": GatewayError,
        "AuthError": AuthError,
        "ForbiddenError": ForbiddenError,
        "NotFoundError": NotFoundError,
        "ConflictError": ConflictError,
        "ValidationError": ValidationError,
        "RateLimitError": RateLimitError,
    }

    def test_readme_exception_names_are_exported(self) -> None:
        """All exceptions mentioned in README are importable from the package."""
        if not README.exists():
            pytest.skip("sdk/README.md not found")

        md_text = README.read_text()
        import mcpgateway_sdk

        for name in self.EXPORTED_EXCEPTIONS:
            if name in md_text:
                assert hasattr(mcpgateway_sdk, name), (
                    f"README mentions {name} but it's not exported from mcpgateway_sdk"
                )


class TestReadmeClientProperties:
    """README references to client properties (mcp_url, auth_headers) must exist."""

    EXPECTED_PROPERTIES = ["mcp_url", "auth_headers"]

    def test_properties_exist(self) -> None:
        for prop in self.EXPECTED_PROPERTIES:
            assert hasattr(MCPGateway, prop), (
                f"README references gw.{prop} but MCPGateway has no '{prop}' attribute"
            )


class TestPyprojectMetadata:
    """pyproject.toml must include README and key metadata for PyPI."""

    @pytest.fixture(scope="class")
    def pyproject(self) -> dict:  # type: ignore[type-arg]
        import tomllib

        toml_path = SDK_ROOT / "pyproject.toml"
        assert toml_path.exists()
        with open(toml_path, "rb") as f:
            return tomllib.load(f)

    def test_readme_configured(self, pyproject: dict) -> None:  # type: ignore[type-arg]
        assert pyproject["project"].get("readme") == "README.md"

    def test_has_classifiers(self, pyproject: dict) -> None:  # type: ignore[type-arg]
        classifiers = pyproject["project"].get("classifiers", [])
        assert len(classifiers) >= 5, f"Expected >=5 classifiers, got {len(classifiers)}"

    def test_has_keywords(self, pyproject: dict) -> None:  # type: ignore[type-arg]
        keywords = pyproject["project"].get("keywords", [])
        assert len(keywords) >= 3, f"Expected >=3 keywords, got {len(keywords)}"

    def test_has_authors(self, pyproject: dict) -> None:  # type: ignore[type-arg]
        authors = pyproject["project"].get("authors", [])
        assert len(authors) >= 1, "Must have at least one author"

    def test_has_multiple_urls(self, pyproject: dict) -> None:  # type: ignore[type-arg]
        urls = pyproject["project"].get("urls", {})
        assert len(urls) >= 3, f"Expected >=3 project URLs, got {len(urls)}"
