"""Path traversal defence for BrowserService.validate_path.

The browser endpoint is the single most security-critical filesystem entry
point in the app: it serves file contents and folder listings to the WebUI.
A bypass here = arbitrary read of any file the server process can see.

The current implementation signals rejection by *raising* (ValueError for
traversal/NUL/unknown folder, FileNotFoundError for non-existent absolute
paths). Either rejection mode is acceptable — these tests assert that the
adversarial input never returns a path *inside* the configured base.
"""

from __future__ import annotations

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

import pytest

from media_server.services.browser_service import BrowserService


@pytest.fixture
def tmp_media_folder():
    """A real temp dir registered as a media folder for the test duration."""
    with tempfile.TemporaryDirectory() as tmp:
        base = Path(tmp).resolve()
        (base / "ok.mp3").write_bytes(b"id3")
        (base / "sub").mkdir()
        (base / "sub" / "nested.mp3").write_bytes(b"id3")

        from media_server.config import MediaFolderConfig
        folders = {"test": MediaFolderConfig(path=str(base), label="Test", enabled=True)}
        with patch("media_server.services.browser_service.settings.media_folders", folders):
            yield base


def _is_rejected(folder_id: str, rel: str) -> bool:
    """Helper: True iff validate_path either raises or returns None."""
    try:
        result = BrowserService.validate_path(folder_id, rel)
    except (ValueError, FileNotFoundError, OSError):
        return True
    return result is None


def test_validate_path_accepts_a_real_file(tmp_media_folder: Path):
    p = BrowserService.validate_path("test", "ok.mp3")
    assert p is not None
    assert p.is_file()
    # Defence-in-depth: resolved path must live inside the base.
    assert tmp_media_folder in p.resolve().parents or p.resolve().parent == tmp_media_folder


def test_validate_path_accepts_nested(tmp_media_folder: Path):
    p = BrowserService.validate_path("test", "sub/nested.mp3")
    assert p is not None


def test_unknown_folder_rejected(tmp_media_folder: Path):
    assert _is_rejected("ghost", "ok.mp3")


def test_dotdot_traversal_rejected(tmp_media_folder: Path):
    assert _is_rejected("test", "../etc/passwd")
    assert _is_rejected("test", "..\\..\\Windows\\System32")
    assert _is_rejected("test", "sub/../../etc/passwd")


def test_absolute_path_rejected(tmp_media_folder: Path):
    assert _is_rejected("test", "/etc/passwd")
    assert _is_rejected("test", "C:\\Windows\\System32")
    assert _is_rejected("test", "C:/Windows")


def test_unc_path_rejected(tmp_media_folder: Path):
    assert _is_rejected("test", "\\\\server\\share")
    assert _is_rejected("test", "//server/share")


def test_null_byte_rejected(tmp_media_folder: Path):
    assert _is_rejected("test", "ok.mp3\x00.png")
    assert _is_rejected("test", "sub\x00/nested.mp3")
