"""Tests for path traversal protection in PDF storage manager (PR #1890).

Verifies:
- _get_safe_file_path blocks traversal, symlinks, and sentinel values
- library_root is resolved on init
- get_absolute_path_from_settings validates resolved paths
"""

import pytest
from unittest.mock import MagicMock, patch

from local_deep_research.constants import (
    FILE_PATH_METADATA_ONLY,
    FILE_PATH_TEXT_ONLY,
    FILE_PATH_BLOB_DELETED,
)
from local_deep_research.research_library.services.pdf_storage_manager import (
    PDFStorageManager,
)


class TestValidateFilesystemPath:
    """Test _get_safe_file_path blocks unsafe paths."""

    def test_traversal_blocked(self, tmp_path):
        """Path with .. escaping library root returns None."""
        manager = PDFStorageManager(
            library_root=str(tmp_path), storage_mode="filesystem"
        )
        result = manager._get_safe_file_path("../../etc/passwd")
        assert result is None

    def test_valid_relative_path(self, tmp_path):
        """Valid relative path inside library root returns resolved Path."""
        sub = tmp_path / "docs"
        sub.mkdir(exist_ok=True)
        test_file = sub / "paper.pdf"
        test_file.write_text("pdf content")

        manager = PDFStorageManager(
            library_root=str(tmp_path), storage_mode="filesystem"
        )
        result = manager._get_safe_file_path("docs/paper.pdf")
        assert result is not None
        assert result.is_relative_to(tmp_path)

    def test_metadata_only_returns_none(self, tmp_path):
        manager = PDFStorageManager(
            library_root=str(tmp_path), storage_mode="filesystem"
        )
        result = manager._get_safe_file_path(FILE_PATH_METADATA_ONLY)
        assert result is None

    def test_text_only_returns_none(self, tmp_path):
        manager = PDFStorageManager(
            library_root=str(tmp_path), storage_mode="filesystem"
        )
        result = manager._get_safe_file_path(FILE_PATH_TEXT_ONLY)
        assert result is None

    def test_blob_deleted_returns_none(self, tmp_path):
        """blob_deleted sentinel should return None (not construct a path)."""
        manager = PDFStorageManager(
            library_root=str(tmp_path), storage_mode="filesystem"
        )
        result = manager._get_safe_file_path(FILE_PATH_BLOB_DELETED)
        assert result is None

    def test_empty_string_returns_none(self, tmp_path):
        manager = PDFStorageManager(
            library_root=str(tmp_path), storage_mode="filesystem"
        )
        result = manager._get_safe_file_path("")
        assert result is None

    def test_none_returns_none(self, tmp_path):
        manager = PDFStorageManager(
            library_root=str(tmp_path), storage_mode="filesystem"
        )
        result = manager._get_safe_file_path(None)
        assert result is None

    def test_symlink_blocked(self, tmp_path):
        """Symlinks are blocked before resolve() dereferences them."""
        manager = PDFStorageManager(
            library_root=str(tmp_path), storage_mode="filesystem"
        )

        # Create a symlink pointing outside library root
        link_path = tmp_path / "evil_link"
        try:
            link_path.symlink_to("/etc/passwd")
        except OSError:
            pytest.skip("Cannot create symlinks in this environment")

        result = manager._get_safe_file_path("evil_link")
        assert result is None


class TestLibraryRootResolved:
    """Verify library_root is resolved on init (prevents late traversal)."""

    def test_library_root_is_resolved(self, tmp_path):
        """library_root should be an absolute resolved path."""
        manager = PDFStorageManager(
            library_root=str(tmp_path), storage_mode="filesystem"
        )
        assert manager.library_root == tmp_path.resolve()
        assert manager.library_root.is_absolute()


class TestGetAbsolutePathFromSettings:
    """Test path traversal protection in get_absolute_path_from_settings."""

    @patch("local_deep_research.utilities.db_utils.get_settings_manager")
    def test_traversal_returns_none(self, mock_settings_mgr, tmp_path):
        from local_deep_research.research_library.utils import (
            get_absolute_path_from_settings,
        )

        mock_mgr = MagicMock()
        mock_mgr.get_setting.return_value = str(tmp_path)
        mock_settings_mgr.return_value = mock_mgr

        result = get_absolute_path_from_settings("../../etc/passwd")
        assert result is None

    @patch("local_deep_research.utilities.db_utils.get_settings_manager")
    def test_valid_path_returns_resolved(self, mock_settings_mgr, tmp_path):
        from local_deep_research.research_library.utils import (
            get_absolute_path_from_settings,
        )

        mock_mgr = MagicMock()
        mock_mgr.get_setting.return_value = str(tmp_path)
        mock_settings_mgr.return_value = mock_mgr

        result = get_absolute_path_from_settings("docs/file.pdf")
        assert result.is_relative_to(tmp_path.resolve())

    @patch("local_deep_research.utilities.db_utils.get_settings_manager")
    def test_empty_path_returns_library_root(self, mock_settings_mgr, tmp_path):
        from local_deep_research.research_library.utils import (
            get_absolute_path_from_settings,
        )

        mock_mgr = MagicMock()
        mock_mgr.get_setting.return_value = str(tmp_path)
        mock_settings_mgr.return_value = mock_mgr

        result = get_absolute_path_from_settings("")
        assert result == tmp_path.resolve()
