# tests/test_path_security.py
"""Comprehensive tests for path security validation."""
import os
import pytest
import tempfile
from pathlib import Path
from unittest.mock import patch


class TestPathTraversal:
    """Tests for path traversal attack prevention."""

    def test_rejects_dot_dot_in_path(self):
        """Should reject obvious directory traversal attempts."""
        from second_opinion_mcp.server import _is_safe_directory
        malicious = Path("/home/../../../etc/passwd")
        assert _is_safe_directory(malicious) is False

    def test_rejects_various_traversal_patterns(self):
        """Should reject various traversal pattern combinations."""
        from second_opinion_mcp.server import _is_safe_directory
        patterns = [
            Path("../../../etc/passwd"),
            Path("foo/../../../bar"),
            Path("safe/../../unsafe"),
            Path("./../../etc"),
            Path("dir/subdir/../../.."),
        ]
        for p in patterns:
            assert _is_safe_directory(p) is False, f"Path {p} should be rejected"

    def test_rejects_hidden_traversal_in_middle(self):
        """Should detect traversal patterns in the middle of paths."""
        from second_opinion_mcp.server import _is_safe_directory
        assert _is_safe_directory(Path("safe/../../unsafe/file")) is False

    def test_rejects_encoded_traversal(self):
        """Should reject encoded traversal patterns if present."""
        from second_opinion_mcp.server import _is_safe_directory
        # URL-encoded .. would be %2e%2e but after decoding it's just ..
        assert _is_safe_directory(Path("safe/../other")) is False


class TestBlockedSystemPaths:
    """Tests for blocked system directory protection."""

    def test_blocks_unix_system_directories(self):
        """Should block access to Unix system directories."""
        from second_opinion_mcp.server import _is_safe_directory
        blocked_dirs = [
            Path("/etc"),
            Path("/etc/passwd"),
            Path("/var/log"),
            Path("/root"),
            Path("/proc/self"),
            Path("/sys/kernel"),
            Path("/boot/grub"),
            Path("/lib/modules"),
        ]
        for p in blocked_dirs:
            assert _is_safe_directory(p) is False, f"Path {p} should be blocked"

    def test_blocks_windows_system_directories(self):
        """Should block access to Windows system directories."""
        from second_opinion_mcp.server import _is_safe_directory
        blocked_dirs = [
            Path("C:/Windows"),
            Path("C:/Windows/System32"),
            Path("C:/Program Files"),
            Path("C:/Program Files (x86)"),
            Path("C:/ProgramData"),
        ]
        for p in blocked_dirs:
            # These may not exist on the test system, but the blocking logic
            # should still identify them as blocked
            assert _is_safe_directory(p) is False, f"Path {p} should be blocked"


class TestAllowedRoots:
    """Tests for allowed roots validation."""

    def test_allows_path_under_allowed_root(self):
        """Should allow paths under configured allowed roots."""
        from second_opinion_mcp.server import _is_safe_directory, ALLOWED_ROOTS

        # Skip if no allowed roots configured (CI environment)
        if not ALLOWED_ROOTS:
            pytest.skip("No allowed roots configured")

        # Test that the function returns a boolean for any path
        # (actual allow/deny depends on runtime ALLOWED_ROOTS config)
        allowed_root = ALLOWED_ROOTS[0]
        test_path = allowed_root / "hypothetical_project"

        result = _is_safe_directory(test_path)
        assert isinstance(result, bool), "Should return boolean"

        # If the allowed root exists, paths under it should be allowed
        if allowed_root.exists():
            assert result is True, f"Path under {allowed_root} should be allowed"

    def test_rejects_path_outside_allowed_roots(self):
        """Should reject paths not under any allowed root."""
        from second_opinion_mcp.server import _is_safe_directory

        # Use a path that's definitely not under typical allowed roots
        outside_path = Path("/unlikely/random/path/12345")
        assert _is_safe_directory(outside_path) is False


class TestSymlinkDetection:
    """Tests for symlink detection in file discovery."""

    def test_discover_skips_symlinks(self):
        """Should skip symlinks when discovering files."""
        from second_opinion_mcp.server import _discover_reviewable_files

        with tempfile.TemporaryDirectory() as tmpdir:
            tmppath = Path(tmpdir)

            # Create a regular file
            regular_file = tmppath / "regular.py"
            regular_file.write_text("# regular file")

            # Create a symlink (if platform supports it)
            symlink_file = tmppath / "symlink.py"
            try:
                symlink_file.symlink_to(regular_file)
            except OSError:
                pytest.skip("Platform does not support symlinks")

            file_list, files_content, total_size = _discover_reviewable_files(tmppath)

            # Symlink should be marked as skipped
            symlink_entries = [f for f in file_list if "symlink" in f.lower()]
            assert any("skipped" in entry for entry in symlink_entries), \
                f"Symlink should be skipped: {file_list}"

    def test_discover_processes_regular_files(self):
        """Should process regular (non-symlink) files."""
        from second_opinion_mcp.server import _discover_reviewable_files

        with tempfile.TemporaryDirectory() as tmpdir:
            tmppath = Path(tmpdir)

            # Create regular files
            (tmppath / "test.py").write_text("print('hello')")
            (tmppath / "readme.txt").write_text("Documentation")

            file_list, files_content, total_size = _discover_reviewable_files(tmppath)

            # Should have found the files
            assert len(files_content) >= 1
            assert total_size > 0


class TestSensitiveFileFiltering:
    """Tests for sensitive file filtering in code review."""

    def test_skips_env_files(self):
        """Should skip .env files that may contain secrets."""
        from second_opinion_mcp.server import _discover_reviewable_files

        with tempfile.TemporaryDirectory() as tmpdir:
            tmppath = Path(tmpdir)

            # Create a .env file
            (tmppath / ".env").write_text("API_KEY=secret123")
            (tmppath / "main.py").write_text("print('hello')")

            file_list, files_content, total_size = _discover_reviewable_files(tmppath)

            # .env should be marked as skipped
            env_entries = [f for f in file_list if ".env" in f]
            assert any("sensitive" in entry.lower() or "skipped" in entry.lower()
                      for entry in env_entries), f".env should be skipped: {file_list}"

    def test_skips_credential_files(self):
        """Should skip files that commonly contain credentials."""
        from second_opinion_mcp.server import _discover_reviewable_files

        with tempfile.TemporaryDirectory() as tmpdir:
            tmppath = Path(tmpdir)

            # Create potential credential files
            (tmppath / "credentials.json").write_text('{"key": "secret"}')
            (tmppath / "main.py").write_text("print('hello')")

            file_list, files_content, total_size = _discover_reviewable_files(tmppath)

            # credentials.json should be marked as skipped
            cred_entries = [f for f in file_list if "credentials" in f.lower()]
            assert any("sensitive" in entry.lower() or "skipped" in entry.lower()
                      for entry in cred_entries), f"credentials.json should be skipped: {file_list}"

    def test_skips_key_files(self):
        """Should skip key/certificate files."""
        from second_opinion_mcp.server import _discover_reviewable_files

        with tempfile.TemporaryDirectory() as tmpdir:
            tmppath = Path(tmpdir)

            # Create key files
            (tmppath / "private.pem").write_text("-----BEGIN PRIVATE KEY-----")
            (tmppath / "main.py").write_text("print('hello')")

            file_list, files_content, total_size = _discover_reviewable_files(tmppath)

            # .pem files should be skipped (by extension)
            # Check that main.py is found but not .pem
            file_names = [Path(f.split()[1]).name if len(f.split()) > 1 else ""
                         for f in file_list if not f.startswith("-")]
            # The .pem should be filtered by extension skip list
            assert "main.py" in str(file_list) or len(files_content) >= 1
