"""High-value tests for security/path_validator.py - Path Validation."""

from pathlib import Path

import pytest

from local_deep_research.security.path_validator import PathValidator


# ---------------------------------------------------------------------------
# validate_safe_path()
# ---------------------------------------------------------------------------


class TestValidateSafePath:
    """Safe path validation with base directory containment."""

    def test_null_bytes_rejected(self, tmp_path):
        with pytest.raises(ValueError, match="[Nn]ull"):
            PathValidator.validate_safe_path("file\x00.txt", tmp_path)

    def test_traversal_blocked(self, tmp_path):
        with pytest.raises(ValueError, match="traversal"):
            PathValidator.validate_safe_path("../../etc/passwd", tmp_path)

    def test_valid_relative_path(self, tmp_path):
        result = PathValidator.validate_safe_path("subdir/file.txt", tmp_path)
        assert result is not None
        assert str(tmp_path) in str(result)

    def test_extension_filtering_valid(self, tmp_path):
        result = PathValidator.validate_safe_path(
            "config.json", tmp_path, required_extensions=(".json", ".yaml")
        )
        assert result is not None
        assert result.suffix == ".json"

    def test_extension_filtering_invalid(self, tmp_path):
        with pytest.raises(ValueError, match="file type"):
            PathValidator.validate_safe_path(
                "script.py", tmp_path, required_extensions=(".json",)
            )

    def test_empty_input_rejected(self, tmp_path):
        with pytest.raises(ValueError, match="Invalid path input"):
            PathValidator.validate_safe_path("", tmp_path)

    def test_none_input_rejected(self, tmp_path):
        with pytest.raises(ValueError, match="Invalid path input"):
            PathValidator.validate_safe_path(None, tmp_path)

    def test_simple_filename(self, tmp_path):
        result = PathValidator.validate_safe_path("readme.txt", tmp_path)
        assert result.name == "readme.txt"


# ---------------------------------------------------------------------------
# validate_local_filesystem_path()
# ---------------------------------------------------------------------------


class TestValidateLocalFilesystemPath:
    """Local filesystem path validation."""

    def test_etc_restricted(self):
        with pytest.raises(ValueError, match="system directories"):
            PathValidator.validate_local_filesystem_path("/etc/passwd")

    def test_sys_restricted(self):
        with pytest.raises(ValueError, match="system directories"):
            PathValidator.validate_local_filesystem_path("/sys/kernel")

    def test_proc_restricted(self):
        with pytest.raises(ValueError, match="system directories"):
            PathValidator.validate_local_filesystem_path("/proc/self/environ")

    def test_dev_restricted(self):
        with pytest.raises(ValueError, match="system directories"):
            PathValidator.validate_local_filesystem_path("/dev/null")

    def test_tilde_expansion(self):
        result = PathValidator.validate_local_filesystem_path(
            "~/Documents", restricted_dirs=[]
        )
        assert str(result).startswith(str(Path.home()))

    def test_tilde_alone(self):
        result = PathValidator.validate_local_filesystem_path(
            "~", restricted_dirs=[]
        )
        assert result == Path.home().resolve()

    def test_control_chars_rejected(self):
        with pytest.raises(ValueError, match="Control characters"):
            PathValidator.validate_local_filesystem_path("/tmp/file\x01name")

    def test_null_bytes_rejected(self):
        with pytest.raises(ValueError, match="[Nn]ull"):
            PathValidator.validate_local_filesystem_path("/tmp/file\x00name")

    def test_dotdot_blocked(self):
        with pytest.raises(ValueError, match="traversal"):
            PathValidator.validate_local_filesystem_path("/tmp/../etc/passwd")

    def test_absolute_unix_path_valid(self):
        result = PathValidator.validate_local_filesystem_path("/tmp")
        assert result.is_absolute()

    def test_relative_path_resolved(self):
        result = PathValidator.validate_local_filesystem_path("somedir")
        assert result.is_absolute()

    def test_empty_rejected(self):
        with pytest.raises(ValueError, match="Invalid path input"):
            PathValidator.validate_local_filesystem_path("")

    def test_none_rejected(self):
        with pytest.raises(ValueError, match="Invalid path input"):
            PathValidator.validate_local_filesystem_path(None)

    def test_custom_restricted_dirs(self):
        with pytest.raises(ValueError, match="system directories"):
            PathValidator.validate_local_filesystem_path(
                "/custom/restricted/path",
                restricted_dirs=[Path("/custom/restricted")],
            )

    def test_boot_restricted(self):
        with pytest.raises(ValueError, match="system directories"):
            PathValidator.validate_local_filesystem_path("/boot/vmlinuz")

    def test_var_log_restricted(self):
        with pytest.raises(ValueError, match="system directories"):
            PathValidator.validate_local_filesystem_path("/var/log/syslog")


# ---------------------------------------------------------------------------
# sanitize_for_filesystem_ops()
# ---------------------------------------------------------------------------


class TestSanitizeForFilesystemOps:
    """Re-sanitization for static analyzers."""

    def test_non_absolute_rejected(self):
        with pytest.raises(ValueError, match="absolute"):
            PathValidator.sanitize_for_filesystem_ops(Path("relative/path"))

    def test_absolute_path_passes(self):
        result = PathValidator.sanitize_for_filesystem_ops(Path("/tmp/safe"))
        assert result == Path("/tmp/safe")

    def test_root_path(self):
        result = PathValidator.sanitize_for_filesystem_ops(Path("/"))
        assert result == Path("/")


# ---------------------------------------------------------------------------
# validate_config_path()
# ---------------------------------------------------------------------------


class TestValidateConfigPath:
    """Config file path validation."""

    def test_etc_prefix_rejected(self):
        with pytest.raises(ValueError, match="restricted"):
            PathValidator.validate_config_path("/etc/config.json")

    def test_proc_prefix_rejected(self):
        with pytest.raises(ValueError, match="restricted"):
            PathValidator.validate_config_path("/proc/config.yaml")

    def test_sys_prefix_rejected(self):
        with pytest.raises(ValueError, match="restricted"):
            PathValidator.validate_config_path("/sys/config.toml")

    def test_dev_prefix_rejected(self):
        with pytest.raises(ValueError, match="restricted"):
            PathValidator.validate_config_path("/dev/config.ini")

    def test_dotdot_blocked(self):
        with pytest.raises(ValueError, match="traversal"):
            PathValidator.validate_config_path("../etc/passwd")

    def test_empty_rejected(self):
        with pytest.raises(ValueError, match="Invalid config path"):
            PathValidator.validate_config_path("")

    def test_none_rejected(self):
        with pytest.raises(ValueError, match="Invalid config path"):
            PathValidator.validate_config_path(None)

    def test_null_bytes_rejected(self):
        with pytest.raises(ValueError, match="[Nn]ull"):
            PathValidator.validate_config_path("/tmp/config\x00.json")

    def test_relative_path_uses_config_root(self, tmp_path):
        result = PathValidator.validate_config_path(
            "settings.json", config_root=str(tmp_path)
        )
        assert str(tmp_path) in str(result)

    def test_invalid_extension_rejected(self, tmp_path):
        # Create a file with invalid extension
        test_file = tmp_path / "script.py"
        test_file.write_text("data")
        with pytest.raises(ValueError, match="file type"):
            PathValidator.validate_config_path(
                "script.py", config_root=str(tmp_path)
            )

    def test_relative_config_path_with_config_root(self, tmp_path):
        cfg = tmp_path / "cfg.json"
        cfg.write_text("{}")
        # validate_config_path uses safe_join("/", path) which requires
        # stripping the leading slash; pass relative-to-root form
        result = PathValidator.validate_config_path(
            "cfg.json", config_root=str(tmp_path)
        )
        assert result.name == "cfg.json"

    def test_absolute_config_path_rejected_for_invalid_extension(
        self, tmp_path
    ):
        script = tmp_path / "config.py"
        script.write_text("data")
        with pytest.raises(ValueError, match="file type|Invalid absolute path"):
            PathValidator.validate_config_path(str(script))


# ---------------------------------------------------------------------------
# SAFE_PATH_PATTERN regex
# ---------------------------------------------------------------------------


class TestSafePathPattern:
    """SAFE_PATH_PATTERN regex validation."""

    def test_alphanumeric(self):
        assert PathValidator.SAFE_PATH_PATTERN.match("hello123")

    def test_dots_and_dashes(self):
        assert PathValidator.SAFE_PATH_PATTERN.match("file-name.txt")

    def test_slashes(self):
        assert PathValidator.SAFE_PATH_PATTERN.match("dir/sub/file.txt")

    def test_spaces_rejected(self):
        assert PathValidator.SAFE_PATH_PATTERN.match("hello world") is None

    def test_special_chars_rejected(self):
        assert PathValidator.SAFE_PATH_PATTERN.match("file;rm -rf") is None

    def test_shell_injection_rejected(self):
        assert PathValidator.SAFE_PATH_PATTERN.match("$(whoami)") is None

    def test_underscore_allowed(self):
        assert PathValidator.SAFE_PATH_PATTERN.match("my_file.txt")


# ---------------------------------------------------------------------------
# CONFIG_EXTENSIONS
# ---------------------------------------------------------------------------


class TestConfigExtensions:
    def test_json_included(self):
        assert ".json" in PathValidator.CONFIG_EXTENSIONS

    def test_yaml_included(self):
        assert ".yaml" in PathValidator.CONFIG_EXTENSIONS
        assert ".yml" in PathValidator.CONFIG_EXTENSIONS

    def test_toml_included(self):
        assert ".toml" in PathValidator.CONFIG_EXTENSIONS
