"""
Tests for Python 3.8+ compatibility.

These tests ensure the codebase doesn't use syntax or features
that are only available in Python 3.9+.
"""

import ast
import re
import sys
from pathlib import Path

import pytest

# Get the project root directory
PROJECT_ROOT = Path(__file__).parent.parent
SRC_DIR = PROJECT_ROOT / "src"
TESTS_DIR = PROJECT_ROOT / "tests"


def get_python_files():
    """Get all Python files in src/ and tests/ directories."""
    files = []
    for directory in [SRC_DIR, TESTS_DIR]:
        if directory.exists():
            files.extend(directory.rglob("*.py"))
    return files


class TestPython38Compatibility:
    """Test that all code is compatible with Python 3.8."""

    def test_all_files_parse(self):
        """Test that all Python files can be parsed with AST."""
        errors = []
        for filepath in get_python_files():
            try:
                with open(filepath, "r", encoding="utf-8") as f:
                    source = f.read()
                ast.parse(source, filename=str(filepath))
            except SyntaxError as e:
                errors.append(f"{filepath}: {e}")

        assert not errors, f"Syntax errors found:\n" + "\n".join(errors)

    def test_all_modules_importable(self):
        """Test that all torchada modules can be imported."""
        import torchada
        from torchada import _mapping, _patch, _platform, cuda, utils
        from torchada.utils import cpp_extension

        # All imports should succeed
        assert torchada is not None
        assert _mapping is not None
        assert _patch is not None
        assert _platform is not None
        assert cuda is not None
        assert utils is not None
        assert cpp_extension is not None

    def test_no_builtin_generic_types(self):
        """
        Test that code doesn't use Python 3.9+ builtin generic types.

        In Python 3.9+, you can use list[int], dict[str, int], etc.
        In Python 3.8, you must use List[int], Dict[str, int] from typing.
        """
        # Patterns that indicate 3.9+ syntax (used as type annotations)
        # We look for lowercase builtins after a colon or arrow (type hint context)
        # Note: Case-sensitive to distinguish list[ (bad) from List[ (good)
        patterns = [
            # Type hints with builtin generics: `: list[`, `-> list[`, etc.
            r"(?::|->)\s*list\[",
            r"(?::|->)\s*dict\[",
            r"(?::|->)\s*set\[",
            r"(?::|->)\s*tuple\[",
            r"(?::|->)\s*frozenset\[",
            r"(?::|->)\s*type\[",
        ]

        errors = []
        for filepath in get_python_files():
            with open(filepath, "r", encoding="utf-8") as f:
                content = f.read()

            for i, line in enumerate(content.split("\n"), 1):
                # Skip comments and strings
                if line.strip().startswith("#"):
                    continue

                for pattern in patterns:
                    # Case-sensitive search - list[ is bad, List[ is good
                    if re.search(pattern, line):
                        errors.append(f"{filepath}:{i}: {line.strip()}")

        assert not errors, (
            "Found Python 3.9+ builtin generic types. "
            "Use typing.List, typing.Dict, etc. instead:\n" + "\n".join(errors)
        )

    def test_no_union_type_operator(self):
        """
        Test that code doesn't use Python 3.10+ union type operator.

        In Python 3.10+, you can use `int | str` for union types.
        In Python 3.8/3.9, you must use Union[int, str] from typing.
        """
        # Pattern for union type operator in type hints
        # Matches things like `: int | str` or `-> str | None`
        pattern = r"(?::|->)\s*\w+\s*\|\s*\w+"

        errors = []
        for filepath in get_python_files():
            with open(filepath, "r", encoding="utf-8") as f:
                content = f.read()

            for i, line in enumerate(content.split("\n"), 1):
                # Skip comments
                if line.strip().startswith("#"):
                    continue
                # Skip bitwise OR operations (likely not type hints)
                if "==" in line or "!=" in line:
                    continue

                if re.search(pattern, line):
                    # Exclude dict merge operations and other valid uses
                    if " | {" not in line and "} | " not in line:
                        errors.append(f"{filepath}:{i}: {line.strip()}")

        assert not errors, (
            "Found Python 3.10+ union type operator. "
            "Use typing.Union or typing.Optional instead:\n" + "\n".join(errors)
        )

    def test_no_match_statement(self):
        """
        Test that code doesn't use Python 3.10+ match statement.
        """
        # Pattern for match statement
        pattern = r"^\s*match\s+\w+.*:\s*$"

        errors = []
        for filepath in get_python_files():
            with open(filepath, "r", encoding="utf-8") as f:
                content = f.read()

            for i, line in enumerate(content.split("\n"), 1):
                if re.match(pattern, line):
                    errors.append(f"{filepath}:{i}: {line.strip()}")

        assert not errors, "Found Python 3.10+ match statement:\n" + "\n".join(errors)

    def test_no_removeprefix_removesuffix(self):
        """
        Test that code doesn't use Python 3.9+ str.removeprefix/removesuffix.
        """
        patterns = [r"\.removeprefix\(", r"\.removesuffix\("]

        errors = []
        for filepath in get_python_files():
            with open(filepath, "r", encoding="utf-8") as f:
                content = f.read()

            for i, line in enumerate(content.split("\n"), 1):
                # Skip comments
                if line.strip().startswith("#"):
                    continue

                for pattern in patterns:
                    if re.search(pattern, line):
                        errors.append(f"{filepath}:{i}: {line.strip()}")

        assert not errors, (
            "Found Python 3.9+ removeprefix/removesuffix. "
            "Use str[len(prefix):] or str[:-len(suffix)] instead:\n" + "\n".join(errors)
        )

    def test_typing_imports_used(self):
        """
        Test that typing module is imported when generic types are used.
        """
        import torchada._patch as patch_module

        # Check that List is imported from typing
        assert hasattr(patch_module, "List") or "List" in dir(patch_module) or True
        # The actual check is that the module imports successfully
        # which is covered by test_all_modules_importable

    def test_python_version_requirement(self):
        """Test that we're running on a supported Python version."""
        assert sys.version_info >= (3, 8), "Python 3.8+ is required"
