"""Protocol conformance tests for contracts/ (Issue #2359).

Verifies:
1. Describable conformance — EncryptedBackend and CompressedBackend implement it.
2. WirableFS conformance — NexusFS implements it.
3. Import cycle safety — contracts/ has zero runtime imports from services/ or core/.
"""

import ast
from pathlib import Path

import pytest


class TestDescribableConformance:
    """Verify concrete types implement the Describable protocol."""

    def test_encrypted_backend_is_describable(self) -> None:
        from nexus.backends.wrappers.encrypted import EncryptedStorage
        from nexus.contracts.describable import Describable

        assert issubclass(EncryptedStorage, Describable)

    def test_compressed_backend_is_describable(self) -> None:
        from nexus.backends.wrappers.compressed import CompressedStorage
        from nexus.contracts.describable import Describable

        assert issubclass(CompressedStorage, Describable)


class TestWirableFSConformance:
    """Verify NexusFS implements the WirableFS protocol."""

    @pytest.mark.asyncio
    def test_nexus_fs_is_wirable(self, tmp_path) -> None:
        from nexus.contracts.wirable_fs import WirableFS
        from nexus.core.nexus_fs import NexusFS
        from tests.testkit import make_test_nexus

        # NexusFS.sys_read exists at class level (method)
        assert callable(getattr(NexusFS, "sys_read", None))

        # Verify protocol is runtime_checkable and importable
        assert hasattr(WirableFS, "__protocol_attrs__") or True

        # Verify an instance satisfies the protocol structurally
        nx = make_test_nexus(tmp_path)
        assert isinstance(nx, WirableFS)


class TestContractsImportCycleSafety:
    """contracts/ must have zero runtime imports from services/ or core/."""

    _CONTRACTS_DIR = Path("src/nexus/contracts")

    def _get_contract_files(self) -> list[Path]:
        if not self._CONTRACTS_DIR.exists():
            pytest.skip("contracts/ directory not found")
        return list(self._CONTRACTS_DIR.glob("*.py"))

    def test_no_runtime_imports_from_core(self) -> None:
        for filepath in self._get_contract_files():
            violations = self._find_runtime_imports(filepath, "nexus.core")
            assert violations == [], (
                f"{filepath.name} has runtime imports from nexus.core: {violations}"
            )

    def test_no_runtime_imports_from_services(self) -> None:
        for filepath in self._get_contract_files():
            violations = self._find_runtime_imports(filepath, "nexus.services")
            assert violations == [], (
                f"{filepath.name} has runtime imports from nexus.services: {violations}"
            )

    @staticmethod
    def _find_runtime_imports(filepath: Path, prefix: str) -> list[str]:
        source = filepath.read_text(encoding="utf-8")
        tree = ast.parse(source, filename=str(filepath))
        violations: list[str] = []

        for node in ast.iter_child_nodes(tree):
            # Skip TYPE_CHECKING blocks
            if isinstance(node, ast.If):
                test = node.test
                if (isinstance(test, ast.Name) and test.id == "TYPE_CHECKING") or (
                    isinstance(test, ast.Attribute) and test.attr == "TYPE_CHECKING"
                ):
                    continue

            if isinstance(node, ast.ImportFrom):
                if node.module and node.module.startswith(prefix):
                    names = ", ".join(a.name for a in node.names)
                    violations.append(f"from {node.module} import {names}")
            elif isinstance(node, ast.Import):
                for alias in node.names:
                    if alias.name.startswith(prefix):
                        violations.append(f"import {alias.name}")

        return violations
