"""
test_security.py - Tests for src/security.py path validation and guardrails.
"""

import platform
import sys
from pathlib import Path
from unittest.mock import patch

import pytest

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

from security import (  # noqa: E402
    SecurityError,
    _is_blocked,
    _is_broad_user_dir,
    validate_watch_request,
)

IS_WINDOWS = platform.system() == "Windows"

# ---------------------------------------------------------------------------
# helpers
# ---------------------------------------------------------------------------


def _patch_windows():
    """Context-manager: make _platform_config behave as if running on Windows."""
    return patch("security.platform.system", return_value="Windows")


def _patch_linux():
    return patch("security.platform.system", return_value="Linux")


# ---------------------------------------------------------------------------
# validate_watch_request — happy path
# ---------------------------------------------------------------------------


class TestValidateHappyPath:
    def test_valid_temp_dir_returns_resolved_path(self, tmp_path):
        result = validate_watch_request(str(tmp_path))
        assert isinstance(result, Path)
        assert result.is_dir()

    def test_returns_absolute_path(self, tmp_path):
        result = validate_watch_request(str(tmp_path))
        assert result.is_absolute()

    def test_recursive_flag_does_not_affect_result(self, tmp_path):
        r1 = validate_watch_request(str(tmp_path), recursive=False)
        r2 = validate_watch_request(str(tmp_path), recursive=True)
        assert r1 == r2


# ---------------------------------------------------------------------------
# validate_watch_request — existence checks
# ---------------------------------------------------------------------------


class TestNonExistentAndFileChecks:
    def test_non_existent_path_raises(self, tmp_path):
        missing = tmp_path / "does_not_exist"
        with pytest.raises(SecurityError, match="does not exist"):
            validate_watch_request(str(missing))

    def test_file_path_raises(self, tmp_path):
        f = tmp_path / "file.txt"
        f.write_text("hello")
        with pytest.raises(SecurityError, match="not a directory"):
            validate_watch_request(str(f))


# ---------------------------------------------------------------------------
# _is_blocked — Windows exact roots
# ---------------------------------------------------------------------------


class TestWindowsExactRoots:
    @pytest.mark.skipif(not IS_WINDOWS, reason="Windows-only path")
    def test_c_drive_root_blocked(self):
        with pytest.raises(SecurityError, match="protected system location"):
            validate_watch_request("C:\\")

    def test_c_drive_root_blocked_via_mock(self):
        with _patch_windows():
            assert _is_blocked(Path("C:\\"))

    def test_c_drive_root_child_not_blocked_by_exact_rule(self):
        """C:\\SomeUserDir should NOT be blocked by the exact-root rule alone."""
        with _patch_windows():
            # Only blocked if it also falls under a subtree-blocked path.
            # A random dir like C:\\MyProjects should pass _is_blocked.
            assert not _is_blocked(Path("C:\\MyProjects"))


# ---------------------------------------------------------------------------
# _is_blocked — Windows system subtrees
# ---------------------------------------------------------------------------


class TestWindowsSystemSubtrees:
    @pytest.mark.parametrize(
        "path_str",
        [
            "C:\\Windows",
            "C:\\Windows\\System32",
            "C:\\Windows\\System32\\drivers",
            "C:\\Windows\\SysWOW64",
            "C:\\Program Files",
            "C:\\Program Files\\Microsoft",
            "C:\\Program Files (x86)",
            "C:\\ProgramData",
            "C:\\ProgramData\\Microsoft\\Windows",
        ],
    )
    def test_windows_system_paths_blocked(self, path_str):
        with _patch_windows():
            assert _is_blocked(Path(path_str)), f"Expected {path_str!r} to be blocked"

    def test_windows_users_child_not_blocked_by_system_subtree(self):
        """C:\\Users\\alice should not be blocked (user's own home dir is fine)."""
        with _patch_windows():
            assert not _is_blocked(Path("C:\\Users\\alice"))


# ---------------------------------------------------------------------------
# _is_broad_user_dir — Windows
# ---------------------------------------------------------------------------


class TestWindowsBroadUserDir:
    def test_c_users_root_blocked(self):
        with _patch_windows():
            assert _is_broad_user_dir(Path("C:\\Users"))

    def test_c_users_alice_allowed(self):
        with _patch_windows():
            assert not _is_broad_user_dir(Path("C:\\Users\\alice"))

    def test_c_users_alice_documents_allowed(self):
        with _patch_windows():
            assert not _is_broad_user_dir(Path("C:\\Users\\alice\\Documents"))

    @pytest.mark.skipif(not IS_WINDOWS, reason="Windows-only live test")
    def test_c_users_itself_raises(self):
        with pytest.raises(SecurityError, match="too broad"):
            validate_watch_request("C:\\Users")


# ---------------------------------------------------------------------------
# _is_blocked — Linux
# ---------------------------------------------------------------------------


class TestLinuxPaths:
    @pytest.mark.parametrize(
        "path_str",
        [
            "/",
            "/etc",
            "/etc/ssh",
            "/sys",
            "/sys/kernel",
            "/proc",
            "/proc/1",
            "/dev",
            "/boot",
            "/usr",
            "/usr/bin",
            "/bin",
            "/sbin",
            "/lib",
            "/lib64",
            "/snap",
        ],
    )
    def test_linux_system_paths_blocked(self, path_str):
        with _patch_linux():
            assert _is_blocked(Path(path_str)), f"Expected {path_str!r} to be blocked"

    def test_linux_home_user_allowed(self):
        with _patch_linux():
            assert not _is_blocked(Path("/home/alice"))

    def test_linux_tmp_allowed(self):
        with _patch_linux():
            assert not _is_blocked(Path("/tmp/myproject"))

    def test_linux_home_root_broad_check(self):
        with _patch_linux():
            assert _is_broad_user_dir(Path("/home"))

    def test_linux_home_alice_not_broad(self):
        with _patch_linux():
            assert not _is_broad_user_dir(Path("/home/alice"))
