"""Tests for filesystem path safety primitives."""

from pathlib import Path

import pytest

from mcp.shared.path_security import (
    PathEscapeError,
    contains_path_traversal,
    is_absolute_path,
    safe_join,
)


@pytest.mark.parametrize(
    ("value", "expected"),
    [
        # Safe: no traversal
        ("a/b/c", False),
        ("readme.txt", False),
        ("", False),
        (".", False),
        ("./a/b", False),
        # Safe: .. balanced by prior descent
        ("a/../b", False),
        ("a/b/../c", False),
        ("a/b/../../c", False),
        # Unsafe: net escape
        ("..", True),
        ("../etc", True),
        ("../../etc/passwd", True),
        ("a/../../b", True),
        ("./../../etc", True),
        # .. as substring, not component — safe
        ("1.0..2.0", False),
        ("foo..bar", False),
        ("..foo", False),
        ("foo..", False),
        # Backslash separator
        ("..\\etc", True),
        ("a\\..\\..\\b", True),
        ("a\\b\\c", False),
        # Mixed separators
        ("a/..\\..\\b", True),
    ],
)
def test_contains_path_traversal(value: str, expected: bool):
    assert contains_path_traversal(value) is expected


@pytest.mark.parametrize(
    ("value", "expected"),
    [
        # Relative
        ("relative/path", False),
        ("file.txt", False),
        ("", False),
        (".", False),
        ("..", False),
        # POSIX absolute
        ("/", True),
        ("/etc/passwd", True),
        ("/a", True),
        # Windows drive
        ("C:", True),
        ("C:\\Windows", True),
        ("c:/foo", True),
        ("Z:\\", True),
        # Windows UNC / backslash-absolute
        ("\\\\server\\share", True),
        ("\\foo", True),
        # Windows drive-relative — discards the join base when drives differ
        ("C:relative", True),
        ("x:y", True),
        ("a:debug", True),
        # Not a drive: digit before colon
        ("1:foo", False),
        # Colon not in position 1
        ("ab:c", False),
        # Non-ASCII letter is not a drive letter
        ("Ω:namespace", False),
        ("é:foo", False),
    ],
)
def test_is_absolute_path(value: str, expected: bool):
    assert is_absolute_path(value) is expected


def test_safe_join_simple(tmp_path: Path):
    result = safe_join(tmp_path, "docs", "readme.txt")
    assert result == tmp_path / "docs" / "readme.txt"


def test_safe_join_resolves_relative_base(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
    monkeypatch.chdir(tmp_path)
    result = safe_join(".", "file.txt")
    assert result == tmp_path / "file.txt"


def test_safe_join_rejects_dotdot_escape(tmp_path: Path):
    with pytest.raises(PathEscapeError, match="escapes base"):
        safe_join(tmp_path, "../../../etc/passwd")


def test_safe_join_rejects_balanced_then_escape(tmp_path: Path):
    with pytest.raises(PathEscapeError, match="escapes base"):
        safe_join(tmp_path, "a/../../etc")


def test_safe_join_allows_balanced_dotdot(tmp_path: Path):
    result = safe_join(tmp_path, "a/../b")
    assert result == tmp_path / "b"


def test_safe_join_rejects_absolute_part(tmp_path: Path):
    with pytest.raises(PathEscapeError, match="is absolute"):
        safe_join(tmp_path, "/etc/passwd")


def test_safe_join_rejects_absolute_in_later_part(tmp_path: Path):
    with pytest.raises(PathEscapeError, match="is absolute"):
        safe_join(tmp_path, "docs", "/etc/passwd")


def test_safe_join_rejects_windows_drive(tmp_path: Path):
    with pytest.raises(PathEscapeError, match="is absolute"):
        safe_join(tmp_path, "C:\\Windows\\System32")


def test_safe_join_rejects_null_byte(tmp_path: Path):
    with pytest.raises(PathEscapeError, match="null byte"):
        safe_join(tmp_path, "file\0.txt")


def test_safe_join_rejects_null_byte_in_later_part(tmp_path: Path):
    with pytest.raises(PathEscapeError, match="null byte"):
        safe_join(tmp_path, "docs", "file\0.txt")


def test_safe_join_rejects_symlink_escape(tmp_path: Path):
    outside = tmp_path / "outside"
    outside.mkdir()
    sandbox = tmp_path / "sandbox"
    sandbox.mkdir()
    (sandbox / "escape").symlink_to(outside)

    with pytest.raises(PathEscapeError, match="escapes base"):
        safe_join(sandbox, "escape", "secret.txt")


def test_safe_join_base_equals_target(tmp_path: Path):
    # Joining nothing (or ".") should return the base itself
    assert safe_join(tmp_path) == tmp_path
    assert safe_join(tmp_path, ".") == tmp_path


def test_path_escape_error_is_value_error():
    with pytest.raises(ValueError):
        safe_join("/tmp", "/etc")
