"""
Tests for uncovered code paths in PathValidator.

Targets:
- validate_config_path: absolute path branch, restricted prefixes, null bytes
- sanitize_for_filesystem_ops: non-absolute path, safe_join failure
- validate_model_path: non-existent file, not-a-file, model_root creation
- validate_local_filesystem_path: control characters, tilde expansion, relative path
"""

import os
from pathlib import Path

import pytest

from local_deep_research.security.path_validator import PathValidator


class TestValidateConfigPathAbsolute:
    """Tests for validate_config_path with absolute paths."""

    def test_absolute_path_rejected_by_safe_join(self, tmp_path):
        """Absolute Unix paths are rejected by safe_join in validate_config_path.

        werkzeug safe_join("/", "/absolute/path") returns None because the
        second argument starts with "/" which is treated as escaping the base.
        This covers the `if safe_path is None: raise ValueError` branch.
        """
        config_file = tmp_path / "test.json"
        config_file.write_text("{}")

        with pytest.raises(ValueError, match="Invalid"):
            PathValidator.validate_config_path(str(config_file))

    def test_config_path_null_bytes(self):
        """Config path with null bytes raises ValueError."""
        with pytest.raises(ValueError, match="Null bytes"):
            PathValidator.validate_config_path("/some/path\x00.json")

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

    def test_config_path_non_string(self):
        """Non-string config path raises ValueError."""
        with pytest.raises(ValueError, match="Invalid config path input"):
            PathValidator.validate_config_path(None)  # type: ignore

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


class TestConfigPathRestrictedPrefixes:
    """Tests for restricted system directory prefixes in config path."""

    @pytest.mark.parametrize(
        "path",
        [
            "etc/passwd",
            "/etc/passwd",
            "proc/self/status",
            "sys/kernel",
            "dev/null",
        ],
    )
    def test_restricted_prefixes_blocked(self, path):
        """Config paths targeting system directories are blocked."""
        with pytest.raises(ValueError, match="restricted system directory"):
            PathValidator.validate_config_path(path)


class TestConfigPathRelative:
    """Tests for validate_config_path with relative paths."""

    def test_relative_config_path_with_valid_extension(self, tmp_path):
        """Relative config path resolves within config_root."""
        config_file = tmp_path / "settings.yaml"
        config_file.write_text("key: value")

        result = PathValidator.validate_config_path(
            "settings.yaml", config_root=str(tmp_path)
        )
        assert result == config_file

    def test_relative_config_path_invalid_extension(self, tmp_path):
        """Relative config path with wrong extension raises ValueError."""
        with pytest.raises(ValueError, match="Invalid file type"):
            PathValidator.validate_config_path(
                "script.sh", config_root=str(tmp_path)
            )


class TestSanitizeForFilesystemOps:
    """Tests for sanitize_for_filesystem_ops."""

    def test_valid_absolute_path(self, tmp_path):
        """Valid absolute path is returned unchanged."""
        result = PathValidator.sanitize_for_filesystem_ops(tmp_path)
        assert result == tmp_path

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


class TestValidateModelPath:
    """Tests for validate_model_path edge cases."""

    def test_model_path_nonexistent_file(self, tmp_path):
        """Model path to nonexistent file raises ValueError."""
        with pytest.raises(ValueError, match="Model file not found"):
            PathValidator.validate_model_path(
                "nonexistent.bin", model_root=str(tmp_path)
            )

    def test_model_path_is_directory(self, tmp_path):
        """Model path pointing to directory raises ValueError."""
        subdir = tmp_path / "model_dir"
        subdir.mkdir()

        with pytest.raises(ValueError, match="not a file"):
            PathValidator.validate_model_path(
                "model_dir", model_root=str(tmp_path)
            )

    def test_model_path_valid_file(self, tmp_path):
        """Valid model file is returned."""
        model_file = tmp_path / "model.bin"
        model_file.write_bytes(b"fake model data")

        result = PathValidator.validate_model_path(
            "model.bin", model_root=str(tmp_path)
        )
        assert result == model_file

    def test_model_path_creates_root_directory(self, tmp_path):
        """Model root directory is created if it doesn't exist."""
        new_root = tmp_path / "new_model_root"

        # The directory doesn't exist yet but will be created
        # Then the file won't exist, so it should raise
        with pytest.raises(ValueError, match="Model file not found"):
            PathValidator.validate_model_path(
                "model.bin", model_root=str(new_root)
            )

        # But the directory should have been created
        assert new_root.exists()


class TestValidateLocalFilesystemPath:
    """Tests for validate_local_filesystem_path edge cases."""

    def test_control_characters_rejected(self):
        """Paths with control characters are rejected."""
        with pytest.raises(ValueError, match="Control characters"):
            PathValidator.validate_local_filesystem_path("/tmp/test\x01file")

    def test_tilde_expansion(self):
        """Tilde is expanded to home directory."""
        # Pass empty restricted_dirs to avoid /root being blocked in CI
        result = PathValidator.validate_local_filesystem_path(
            "~", restricted_dirs=[]
        )
        assert result == Path.home().resolve()

    def test_tilde_with_subpath(self):
        """Tilde with subpath is expanded correctly."""
        # Pass empty restricted_dirs to avoid /root being blocked in CI
        result = PathValidator.validate_local_filesystem_path(
            "~/Documents", restricted_dirs=[]
        )
        expected = (Path.home() / "Documents").resolve()
        assert result == expected

    def test_relative_path_resolved_to_cwd(self):
        """Relative path is resolved relative to CWD."""
        result = PathValidator.validate_local_filesystem_path("some_dir")
        expected = (Path(os.getcwd()) / "some_dir").resolve()
        assert result == expected

    def test_restricted_directories_blocked(self):
        """System directories like /etc are blocked."""
        with pytest.raises(
            ValueError, match="Cannot access system directories"
        ):
            PathValidator.validate_local_filesystem_path("/etc/passwd")

    def test_custom_restricted_dirs(self, tmp_path):
        """Custom restricted directories are respected."""
        restricted = tmp_path / "restricted"
        restricted.mkdir()

        with pytest.raises(
            ValueError, match="Cannot access system directories"
        ):
            PathValidator.validate_local_filesystem_path(
                str(restricted / "file.txt"),
                restricted_dirs=[restricted],
            )

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

    def test_null_bytes_blocked(self):
        """Null bytes in path are blocked."""
        with pytest.raises(ValueError, match="Null bytes"):
            PathValidator.validate_local_filesystem_path("/tmp/file\x00.txt")


class TestValidateDataPath:
    """Tests for validate_data_path."""

    def test_valid_data_path(self, tmp_path):
        """Valid relative data path is accepted."""
        result = PathValidator.validate_data_path(
            "subdir/file.txt", str(tmp_path)
        )
        assert result.parent.name == "subdir"
        assert result.name == "file.txt"

    def test_data_path_traversal_blocked(self, tmp_path):
        """Data path traversal is blocked."""
        with pytest.raises(ValueError):
            PathValidator.validate_data_path("../../etc/passwd", str(tmp_path))
