"""Edge-case tests for path_validator — control chars, null bytes, and case normalization."""

import tempfile
from pathlib import Path

import pytest

from local_deep_research.security.path_validator import PathValidator


class TestControlCharactersRejected:
    """Verify various control characters (ord < 32) are rejected."""

    def test_tab_character_rejected(self):
        """Tab (\\t, ord 9) should be rejected by validate_local_filesystem_path."""
        with pytest.raises(ValueError, match="[Cc]ontrol"):
            PathValidator.validate_local_filesystem_path("/tmp/test\tdir")

    def test_bell_character_rejected(self):
        """Bell (\\x07, ord 7) should be rejected by validate_local_filesystem_path."""
        with pytest.raises(ValueError, match="[Cc]ontrol"):
            PathValidator.validate_local_filesystem_path("/tmp/test\x07dir")

    def test_newline_in_path_rejected(self):
        """Newline (\\n, ord 10) should be rejected by validate_local_filesystem_path."""
        with pytest.raises(ValueError, match="[Cc]ontrol"):
            PathValidator.validate_local_filesystem_path("/tmp/test\ndir")


class TestMultipleNullBytesStripped:
    """Verify validate_config_path strips ALL null bytes, not just the first."""

    def test_multiple_null_bytes_all_stripped(self):
        """Multiple null bytes in config path should all be removed."""
        with tempfile.TemporaryDirectory() as tmpdir:
            # Create a settings.json file
            config_file = Path(tmpdir) / "settings.json"
            config_file.write_text('{"key": "value"}')

            # "s\x00e\x00ttings.json" → "settings.json" after null removal
            result = PathValidator.validate_config_path(
                "s\x00e\x00ttings.json",
                config_root=tmpdir,
            )
            assert result.exists()
            assert result.name == "settings.json"


class TestEtcWithDifferentCasingBlocked:
    """Verify .lower() normalization blocks case-varied restricted paths."""

    def test_etc_with_different_casing_blocked(self):
        """/ETC/passwd should be blocked via .lower() normalization in validate_config_path."""
        with pytest.raises(ValueError, match="restricted"):
            PathValidator.validate_config_path("/ETC/passwd")

    def test_etc_mixed_case_blocked(self):
        """/Etc/shadow should also be blocked."""
        with pytest.raises(ValueError, match="restricted"):
            PathValidator.validate_config_path("/Etc/shadow")


class TestSafeJoinReturnsNoneRaisesValueError:
    """Verify path traversal via safe_join → None → ValueError."""

    @pytest.fixture
    def temp_base_dir(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            yield Path(tmpdir)

    def test_safe_join_returns_none_raises_valueerror(self, temp_base_dir):
        """Path traversal attempt where safe_join returns None must raise ValueError."""
        with pytest.raises(ValueError):
            PathValidator.validate_safe_path(
                "../../../../../etc/passwd",
                temp_base_dir,
            )
