"""Tests for doublestar (**) pattern matching according to old syftbox ACL behavior."""

import os
import shutil
import sys
import tempfile
import unittest
from pathlib import Path

import yaml

sys.path.insert(0, str(Path(__file__).parent.parent / "src"))

import syft_perm  # noqa: E402


class TestDoublestarPatterns(unittest.TestCase):
    """Test doublestar (**) pattern matching behavior matching old syftbox ACL."""

    def setUp(self):
        """Create a temporary directory for test files."""
        self.test_dir = tempfile.mkdtemp(prefix="syft_perm_test_")
        self.test_users = ["alice@example.com", "bob@example.com", "charlie@example.com"]

    def tearDown(self):
        """Clean up test directory."""
        if os.path.exists(self.test_dir):
            shutil.rmtree(self.test_dir)

    def test_doublestar_matches_everything(self):
        """Test that ** pattern matches everything at any depth."""
        # Create various files and directories
        files = [
            "root.txt",
            "config.json",
            "src/main.py",
            "src/lib/utils.py",
            "docs/readme.md",
            "tests/unit/test_main.py",
            "deeply/nested/path/to/file.dat",
            ".hidden/secret.key",  # Hidden directory
            "normal/.gitignore",  # Hidden file
        ]

        for file_path in files:
            full_path = Path(self.test_dir) / file_path
            full_path.parent.mkdir(parents=True, exist_ok=True)
            full_path.write_text(f"content of {file_path}")

        # Create syft.pub.yaml with ** pattern
        yaml_file = Path(self.test_dir) / "syft.pub.yaml"
        yaml_content = {"rules": [{"pattern": "**", "access": {"read": ["alice@example.com"]}}]}
        with open(yaml_file, "w") as f:
            yaml.dump(yaml_content, f)

        # Verify all files match
        for file_path in files:
            syft_file = syft_perm.open(Path(self.test_dir) / file_path)
            self.assertTrue(
                syft_file.has_read_access("alice@example.com"), f"** should match {file_path}"
            )
            self.assertFalse(syft_file.has_write_access("alice@example.com"))

            # Others have no access
            self.assertFalse(syft_file.has_read_access("bob@example.com"))

    def test_doublestar_ext_matches_at_any_depth(self):
        """Test that **/*.ext matches files with .ext at any depth including root."""
        # Create .py files at various depths
        py_files = [
            "script.py",  # Root level
            "main.py",  # Root level
            "src/app.py",  # One level deep
            "src/lib/utils.py",  # Two levels deep
            "tests/unit/test_app.py",  # Two levels deep
            "very/deep/path/module.py",  # Very deep
        ]

        # Create non-.py files
        other_files = ["readme.txt", "src/config.json", "tests/data.csv"]

        all_files = py_files + other_files
        for file_path in all_files:
            full_path = Path(self.test_dir) / file_path
            full_path.parent.mkdir(parents=True, exist_ok=True)
            full_path.write_text("content")

        # Create syft.pub.yaml with **/*.py pattern
        yaml_file = Path(self.test_dir) / "syft.pub.yaml"
        yaml_content = {"rules": [{"pattern": "**/*.py", "access": {"write": ["bob@example.com"]}}]}
        with open(yaml_file, "w") as f:
            yaml.dump(yaml_content, f)

        # Verify all .py files match including root level
        for py_file in py_files:
            syft_file = syft_perm.open(Path(self.test_dir) / py_file)
            self.assertTrue(
                syft_file.has_write_access("bob@example.com"),
                f"**/*.py should match {py_file} even at root",
            )
            self.assertTrue(syft_file.has_read_access("bob@example.com"))  # Via hierarchy

        # Verify non-.py files don't match
        for other_file in other_files:
            syft_file = syft_perm.open(Path(self.test_dir) / other_file)
            self.assertFalse(
                syft_file.has_read_access("bob@example.com"),
                f"**/*.py should not match {other_file}",
            )

    def test_dir_doublestar_matches_everything_under_dir(self):
        """Test that dir/** matches everything under dir but not dir itself."""
        # Create directory structure
        docs_dir = Path(self.test_dir) / "docs"
        src_dir = Path(self.test_dir) / "src"

        # Files under docs/
        docs_files = [
            "docs/readme.md",
            "docs/guide.pdf",
            "docs/api/reference.html",
            "docs/examples/tutorial.md",
            "docs/deep/nested/file.txt",
        ]

        # Files not under docs/
        other_files = [
            "readme.md",  # Root level
            "src/main.py",
            "src/docs/fake.md",  # Contains 'docs' but not under docs/
        ]

        all_files = docs_files + other_files
        for file_path in all_files:
            full_path = Path(self.test_dir) / file_path
            full_path.parent.mkdir(parents=True, exist_ok=True)
            full_path.write_text("content")

        # Create folders to test that directories themselves don't match
        docs_dir.mkdir(exist_ok=True)
        src_dir.mkdir(exist_ok=True)

        # Create syft.pub.yaml with docs/** pattern
        yaml_file = Path(self.test_dir) / "syft.pub.yaml"
        yaml_content = {
            "rules": [{"pattern": "docs/**", "access": {"admin": ["charlie@example.com"]}}]
        }
        with open(yaml_file, "w") as f:
            yaml.dump(yaml_content, f)

        # Verify all files under docs/ match
        for docs_file in docs_files:
            syft_file = syft_perm.open(Path(self.test_dir) / docs_file)
            self.assertTrue(
                syft_file.has_admin_access("charlie@example.com"),
                f"docs/** should match {docs_file}",
            )

        # Verify files NOT under docs/ don't match
        for other_file in other_files:
            syft_file = syft_perm.open(Path(self.test_dir) / other_file)
            self.assertFalse(
                syft_file.has_read_access("charlie@example.com"),
                f"docs/** should not match {other_file}",
            )

        # Verify the docs directory itself
        # In the implementation, folders check both "path" and "path/" against patterns
        # So docs/** will match the docs folder when checked as "docs/"
        # This is the actual behavior - documenting it
        # The implementation adds "/" to folder paths when checking, so "docs/" matches "docs/**"
        # This might be intentional to allow folder permissions

    def test_complex_middle_doublestar_patterns(self):
        """Test complex patterns like dir/**/subdir/*.ext where ** matches zero or more dirs."""
        # Create files that should match src/**/test/*.py
        matching_files = [
            "src/test/unit.py",  # Zero intermediate dirs
            "src/app/test/integration.py",  # One intermediate dir
            "src/lib/utils/test/helper.py",  # Two intermediate dirs
            "src/a/b/c/test/deep.py",  # Many intermediate dirs
        ]

        # Create files that should NOT match
        non_matching_files = [
            "src/main.py",  # Not under test/
            "test/unit.py",  # Not under src/
            "src/test/data.json",  # Wrong extension
            "src/testing/unit.py",  # Wrong subdirectory name
            "other/test/unit.py",  # Wrong root directory
        ]

        all_files = matching_files + non_matching_files
        for file_path in all_files:
            full_path = Path(self.test_dir) / file_path
            full_path.parent.mkdir(parents=True, exist_ok=True)
            full_path.write_text("content")

        # Create syft.pub.yaml with complex pattern
        yaml_file = Path(self.test_dir) / "syft.pub.yaml"
        yaml_content = {
            "rules": [{"pattern": "src/**/test/*.py", "access": {"create": ["alice@example.com"]}}]
        }
        with open(yaml_file, "w") as f:
            yaml.dump(yaml_content, f)

        # Verify matching files
        for match_file in matching_files:
            syft_file = syft_perm.open(Path(self.test_dir) / match_file)
            self.assertTrue(
                syft_file.has_create_access("alice@example.com"),
                f"src/**/test/*.py should match {match_file}",
            )
            self.assertTrue(syft_file.has_read_access("alice@example.com"))  # Via hierarchy

        # Verify non-matching files
        for non_match in non_matching_files:
            syft_file = syft_perm.open(Path(self.test_dir) / non_match)
            self.assertFalse(
                syft_file.has_read_access("alice@example.com"),
                f"src/**/test/*.py should not match {non_match}",
            )

    def test_multiple_doublestar_positions(self):
        """Test patterns with ** at multiple positions like **/name/**."""
        # Create files matching **/docs/**
        # The pattern **/docs/** requires something after docs
        matching_files = [
            "docs/readme.md",  # docs/ followed by something
            "docs/api/spec.yaml",  # docs/ followed by something
            "src/docs/guide.md",  # docs/ followed by something
            "src/docs/examples/tutorial.py",  # docs/ followed by something
            "a/b/c/docs/d/e/f.txt",  # docs/ followed by something
        ]

        # Create only the actual files, not directories
        for file_path in matching_files:
            full_path = Path(self.test_dir) / file_path
            full_path.parent.mkdir(parents=True, exist_ok=True)
            full_path.write_text("content")

        # Create non-matching regular files
        for file_path in ["readme.md", "src/documentation/guide.md", "src/main.py"]:
            full_path = Path(self.test_dir) / file_path
            full_path.parent.mkdir(parents=True, exist_ok=True)
            full_path.write_text("content")

        # Create the directories mentioned in non_matching_files
        (Path(self.test_dir) / "docs").mkdir(exist_ok=True)
        (Path(self.test_dir) / "src" / "docs").mkdir(parents=True, exist_ok=True)

        # Create syft.pub.yaml with **/docs/** pattern
        yaml_file = Path(self.test_dir) / "syft.pub.yaml"
        yaml_content = {
            "rules": [{"pattern": "**/docs/**", "access": {"read": ["bob@example.com"]}}]
        }
        with open(yaml_file, "w") as f:
            yaml.dump(yaml_content, f)

        # Verify matching files
        for match_file in matching_files:
            syft_file = syft_perm.open(Path(self.test_dir) / match_file)
            self.assertTrue(
                syft_file.has_read_access("bob@example.com"),
                f"**/docs/** should match {match_file}",
            )

        # Verify non-matching files (only check regular files)
        for non_match in ["readme.md", "src/documentation/guide.md", "src/main.py"]:
            syft_file = syft_perm.open(Path(self.test_dir) / non_match)
            self.assertFalse(
                syft_file.has_read_access("bob@example.com"),
                f"**/docs/** should not match {non_match}",
            )

        # Verify directories don't match
        syft_docs_dir = syft_perm.open(Path(self.test_dir) / "docs")
        self.assertFalse(
            syft_docs_dir.has_read_access("bob@example.com"),
            "**/docs/** should not match bare docs directory",
        )

        syft_src_docs = syft_perm.open(Path(self.test_dir) / "src" / "docs")
        self.assertFalse(
            syft_src_docs.has_read_access("bob@example.com"),
            "**/docs/** should not match bare src/docs directory",
        )

    def test_doublestar_dotfiles_and_hidden(self):
        """Test that ** patterns match hidden files and directories."""
        # Create hidden files and directories
        hidden_files = [
            ".env",  # Hidden file at root
            ".config/settings.json",  # File in hidden directory
            "src/.gitignore",  # Hidden file in normal directory
            ".hidden/.secret/key.pem",  # Deeply hidden
            "normal/.hidden/file.txt",  # Hidden under normal
        ]

        for file_path in hidden_files:
            full_path = Path(self.test_dir) / file_path
            full_path.parent.mkdir(parents=True, exist_ok=True)
            full_path.write_text("secret content")

        # Create syft.pub.yaml with ** pattern
        yaml_file = Path(self.test_dir) / "syft.pub.yaml"
        yaml_content = {"rules": [{"pattern": "**", "access": {"write": ["alice@example.com"]}}]}
        with open(yaml_file, "w") as f:
            yaml.dump(yaml_content, f)

        # Verify all hidden files are matched
        for hidden_file in hidden_files:
            syft_file = syft_perm.open(Path(self.test_dir) / hidden_file)
            self.assertTrue(
                syft_file.has_write_access("alice@example.com"),
                f"** should match hidden file {hidden_file}",
            )

    def test_doublestar_case_sensitivity(self):
        """Test that doublestar patterns are case-sensitive."""
        # Create files with different cases
        files = [
            "README.MD",  # Uppercase extension
            "readme.md",  # Lowercase extension
            "ReadMe.Md",  # Mixed case
            "src/Main.PY",  # Uppercase extension
            "src/main.py",  # Lowercase extension
            "TEST/file.TXT",  # Uppercase directory and extension
            "test/file.txt",  # Lowercase directory and extension
        ]

        for file_path in files:
            full_path = Path(self.test_dir) / file_path
            full_path.parent.mkdir(parents=True, exist_ok=True)
            full_path.write_text("content")

        # Create syft.pub.yaml with case-sensitive patterns
        yaml_file = Path(self.test_dir) / "syft.pub.yaml"
        yaml_content = {
            "rules": [
                {
                    "pattern": "**/*.md",  # Lowercase .md only
                    "access": {"read": ["alice@example.com"]},
                },
                {
                    "pattern": "**/*.PY",  # Uppercase .PY only
                    "access": {"write": ["bob@example.com"]},
                },
                {
                    "pattern": "TEST/**",  # Uppercase TEST only
                    "access": {"admin": ["charlie@example.com"]},
                },
            ]
        }
        with open(yaml_file, "w") as f:
            yaml.dump(yaml_content, f)

        # Test lowercase .md pattern
        syft_lower_md = syft_perm.open(Path(self.test_dir) / "readme.md")
        self.assertTrue(syft_lower_md.has_read_access("alice@example.com"))

        syft_upper_md = syft_perm.open(Path(self.test_dir) / "README.MD")
        self.assertFalse(syft_upper_md.has_read_access("alice@example.com"))

        # Test uppercase .PY pattern
        syft_upper_py = syft_perm.open(Path(self.test_dir) / "src/Main.PY")
        self.assertTrue(syft_upper_py.has_write_access("bob@example.com"))

        syft_lower_py = syft_perm.open(Path(self.test_dir) / "src/main.py")
        self.assertFalse(syft_lower_py.has_read_access("bob@example.com"))

        # Test uppercase TEST directory pattern
        syft_upper_test = syft_perm.open(Path(self.test_dir) / "TEST/file.TXT")
        self.assertTrue(syft_upper_test.has_admin_access("charlie@example.com"))

        syft_lower_test = syft_perm.open(Path(self.test_dir) / "test/file.txt")
        self.assertFalse(syft_lower_test.has_read_access("charlie@example.com"))

    def test_doublestar_vs_single_star_behavior(self):
        """Test the difference between * and ** in pattern matching."""
        # Create nested structure
        files = [
            "file.txt",  # Root level
            "dir/file.txt",  # One level deep
            "dir/sub/file.txt",  # Two levels deep
        ]

        for file_path in files:
            full_path = Path(self.test_dir) / file_path
            full_path.parent.mkdir(parents=True, exist_ok=True)
            full_path.write_text("content")

        # Create two permission files to test the difference
        # First, test single star
        single_star_dir = Path(self.test_dir) / "single_star_test"
        single_star_dir.mkdir(parents=True)

        for file_path in files:
            full_path = single_star_dir / file_path
            full_path.parent.mkdir(parents=True, exist_ok=True)
            full_path.write_text("content")

        single_yaml = single_star_dir / "syft.pub.yaml"
        single_content = {
            "rules": [
                {
                    "pattern": "*.txt",  # Single star - only current directory
                    "access": {"read": ["alice@example.com"]},
                }
            ]
        }
        with open(single_yaml, "w") as f:
            yaml.dump(single_content, f)

        # Test single star - should only match root level
        syft_single_root = syft_perm.open(single_star_dir / "file.txt")
        self.assertTrue(
            syft_single_root.has_read_access("alice@example.com"),
            "*.txt should match root level file",
        )

        syft_single_nested = syft_perm.open(single_star_dir / "dir/file.txt")
        self.assertFalse(
            syft_single_nested.has_read_access("alice@example.com"),
            "*.txt should NOT match nested file",
        )

        # Now test double star in main test directory
        double_yaml = Path(self.test_dir) / "syft.pub.yaml"
        double_content = {
            "rules": [
                {
                    "pattern": "**/*.txt",  # Double star - any depth
                    "access": {"read": ["bob@example.com"]},
                }
            ]
        }
        with open(double_yaml, "w") as f:
            yaml.dump(double_content, f)

        # Test double star - should match all depths
        for file_path in files:
            syft_file = syft_perm.open(Path(self.test_dir) / file_path)
            self.assertTrue(
                syft_file.has_read_access("bob@example.com"),
                f"**/*.txt should match {file_path} at any depth",
            )

    def test_doublestar_pattern_specificity_scoring(self):
        """Test that patterns are sorted by specificity with ** having lowest score."""
        # Create test file
        test_file = Path(self.test_dir) / "src/lib/utils.py"
        test_file.parent.mkdir(parents=True, exist_ok=True)
        test_file.write_text("content")

        # Create rules with different specificity levels
        yaml_file = Path(self.test_dir) / "syft.pub.yaml"
        yaml_content = {
            "rules": [
                {
                    "pattern": "**",  # Least specific (-100 score)
                    "access": {"read": ["alice@example.com"]},
                },
                {"pattern": "**/*.py", "access": {"write": ["bob@example.com"]}},  # More specific
                {
                    "pattern": "src/**/*.py",  # Even more specific
                    "access": {"create": ["charlie@example.com"]},
                },
                {
                    "pattern": "src/lib/utils.py",  # Most specific (exact match)
                    "access": {"admin": ["dave@example.com"]},
                },
            ]
        }
        with open(yaml_file, "w") as f:
            yaml.dump(yaml_content, f)

        # The most specific pattern should win
        syft_file = syft_perm.open(test_file)

        # Should have admin access from most specific rule
        self.assertTrue(syft_file.has_admin_access("dave@example.com"))
        self.assertTrue(syft_file.has_write_access("dave@example.com"))  # Via hierarchy
        self.assertTrue(syft_file.has_create_access("dave@example.com"))  # Via hierarchy
        self.assertTrue(syft_file.has_read_access("dave@example.com"))  # Via hierarchy

        # Should NOT have permissions from less specific rules
        self.assertFalse(syft_file.has_create_access("charlie@example.com"))
        self.assertFalse(syft_file.has_write_access("bob@example.com"))
        self.assertFalse(syft_file.has_read_access("alice@example.com"))

        # Verify pattern matching reason shows most specific pattern
        has_admin, reasons = syft_file._check_permission_with_reasons("dave@example.com", "admin")
        self.assertTrue(has_admin)
        self.assertTrue(any("src/lib/utils.py" in r for r in reasons))

    def test_doublestar_empty_path_components(self):
        """Test that ** can match zero directories (empty path components)."""
        # Create files to test zero-match behavior
        files = [
            "src/test.py",  # Should match src/**/test.py (zero dirs between)
            "src/lib/test.py",  # Should match src/**/test.py (one dir between)
            "api/v1/endpoints.py",  # Should match api/**/endpoints.py
        ]

        for file_path in files:
            full_path = Path(self.test_dir) / file_path
            full_path.parent.mkdir(parents=True, exist_ok=True)
            full_path.write_text("content")

        # Create rules that test zero-match behavior
        yaml_file = Path(self.test_dir) / "syft.pub.yaml"
        yaml_content = {
            "rules": [
                {"pattern": "src/**/test.py", "access": {"read": ["alice@example.com"]}},
                {"pattern": "api/**/endpoints.py", "access": {"write": ["bob@example.com"]}},
            ]
        }
        with open(yaml_file, "w") as f:
            yaml.dump(yaml_content, f)

        # Test that ** matches zero directories
        syft_zero_match = syft_perm.open(Path(self.test_dir) / "src/test.py")
        self.assertTrue(
            syft_zero_match.has_read_access("alice@example.com"),
            "src/**/test.py should match src/test.py (zero intermediate dirs)",
        )

        # Also test with intermediate directories
        syft_one_match = syft_perm.open(Path(self.test_dir) / "src/lib/test.py")
        self.assertTrue(
            syft_one_match.has_read_access("alice@example.com"),
            "src/**/test.py should match src/lib/test.py",
        )

        # Test another zero-match case
        syft_api = syft_perm.open(Path(self.test_dir) / "api/v1/endpoints.py")
        self.assertTrue(
            syft_api.has_write_access("bob@example.com"),
            "api/**/endpoints.py should match api/v1/endpoints.py",
        )


if __name__ == "__main__":
    unittest.main()
