"""
Deep coverage tests for security/path_validator.py.

Targets uncovered paths:
- sanitize_for_filesystem_ops: relative path rejection, safe_join None
- validate_local_filesystem_path: tilde-only expansion, relative path, path traversal
- validate_config_path: empty input, sys/dev prefix, Windows absolute, absolute non-config ext
- validate_safe_path: exception re-raise wrapping
"""

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

import pytest

from local_deep_research.security.path_validator import PathValidator


class TestSanitizeForFilesystemOps:
    """Cover sanitize_for_filesystem_ops."""

    def test_rejects_relative_path(self):
        """Relative paths raise ValueError."""
        with pytest.raises(ValueError, match="must be absolute"):
            PathValidator.sanitize_for_filesystem_ops(Path("relative/path"))

    def test_accepts_absolute_path(self):
        """Valid absolute path passes through."""
        result = PathValidator.sanitize_for_filesystem_ops(Path("/tmp/test"))
        assert result == Path("/tmp/test")

    def test_returns_none_when_safe_join_fails(self):
        """Raises ValueError when safe_join returns None."""
        with patch(
            "local_deep_research.security.path_validator.safe_join",
            return_value=None,
        ):
            with pytest.raises(ValueError, match="security sanitization"):
                PathValidator.sanitize_for_filesystem_ops(
                    Path("/some/absolute/path")
                )


class TestValidateLocalFilesystemPathAdditional:
    """Cover additional branches in validate_local_filesystem_path."""

    def test_tilde_only_expands_to_home(self):
        """~ alone expands to home directory."""
        home = Path.home()
        # Skip if home is a restricted dir
        restricted = [
            Path("/etc"),
            Path("/sys"),
            Path("/proc"),
            Path("/dev"),
            Path("/root"),
            Path("/boot"),
            Path("/var/log"),
        ]
        for r in restricted:
            if home == r or str(home).startswith(str(r)):
                pytest.skip(f"Home dir {home} is restricted")

        result = PathValidator.validate_local_filesystem_path("~")
        assert result == home.resolve()

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

    def test_relative_path_resolved_from_cwd(self):
        """Relative paths resolve from cwd."""
        with tempfile.TemporaryDirectory() as tmpdir:
            with patch("os.getcwd", return_value=tmpdir):
                result = PathValidator.validate_local_filesystem_path(
                    "subdir", restricted_dirs=[]
                )
                assert result == Path(tmpdir).resolve() / "subdir"

    def test_relative_path_safe_join_none(self):
        """Relative path raises when safe_join returns None."""
        with patch(
            "local_deep_research.security.path_validator.safe_join",
            return_value=None,
        ):
            with pytest.raises(ValueError, match="security validation"):
                PathValidator.validate_local_filesystem_path("some_path")

    @pytest.mark.skipif(sys.platform == "win32", reason="Unix test")
    def test_var_log_blocked(self):
        """Blocks /var/log directory."""
        with pytest.raises(ValueError, match="system"):
            PathValidator.validate_local_filesystem_path("/var/log/syslog")

    @pytest.mark.skipif(sys.platform == "win32", reason="Unix test")
    def test_boot_blocked(self):
        """Blocks /boot directory."""
        with pytest.raises(ValueError, match="system"):
            PathValidator.validate_local_filesystem_path("/boot/vmlinuz")


class TestValidateConfigPathAdditional:
    """Cover additional branches in validate_config_path."""

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

    def test_none_config_path_rejected(self):
        """None raises ValueError."""
        with pytest.raises(ValueError):
            PathValidator.validate_config_path(None)

    def test_sys_prefix_blocked(self):
        """Paths starting with /sys/ are blocked."""
        with pytest.raises(ValueError, match="restricted"):
            PathValidator.validate_config_path("/sys/config.json")

    def test_dev_prefix_blocked(self):
        """Paths starting with /dev are blocked."""
        with pytest.raises(ValueError, match="restricted"):
            PathValidator.validate_config_path("/dev/null")

    def test_bare_restricted_dir_blocked(self):
        """Bare restricted dir name (no trailing slash) is blocked."""
        with pytest.raises(ValueError, match="restricted"):
            PathValidator.validate_config_path("/etc")

    def test_absolute_path_wrong_extension_rejected(self):
        """Absolute path with non-config extension is rejected."""
        # Create temp file in a path that safe_join("/", ...) can resolve
        with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as f:
            path = f.name
        try:
            with pytest.raises(ValueError) as exc_info:
                PathValidator.validate_config_path(path)
            error_msg = str(exc_info.value).lower()
            assert (
                "config file type" in error_msg or "absolute path" in error_msg
            )
        finally:
            import os

            os.unlink(path)

    def test_absolute_path_nonexistent_file_rejected(self):
        """Absolute path to nonexistent config file is rejected."""
        # safe_join("/", path) may return None for some temp paths
        # so we just check for ValueError regardless of exact message
        with pytest.raises(ValueError):
            PathValidator.validate_config_path("/tmp/nonexistent_config.json")

    def test_absolute_safe_join_returns_none(self):
        """Absolute path raises when safe_join returns None."""
        with patch(
            "local_deep_research.security.path_validator.safe_join",
            return_value=None,
        ):
            with pytest.raises(ValueError, match="absolute path"):
                PathValidator.validate_config_path("/valid/config.json")


class TestValidateSafePathExceptionWrapping:
    """Cover the exception wrapping in validate_safe_path."""

    def test_null_byte_in_safe_path(self):
        """Null bytes in path raise ValueError."""
        with tempfile.TemporaryDirectory() as tmpdir:
            with pytest.raises(ValueError, match="Null bytes"):
                PathValidator.validate_safe_path("file\x00.txt", Path(tmpdir))
