"""Unit tests for path validation utilities."""

import tempfile
from pathlib import Path

import pytest

from app.config import Settings
from app.utils.path_utils import validate_log_path


@pytest.fixture
def _mock_settings(monkeypatch: pytest.MonkeyPatch) -> None:
    """Mock get_settings to return test settings with default allowed directories."""

    def mock_get_settings() -> Settings:
        return Settings(
            database_path=":memory:",
            fail2ban_socket="/tmp/fake.sock",
            fail2ban_config_dir="/tmp/fail2ban",
            session_secret="test-secret-key-do-not-use-in-production",
        )

    monkeypatch.setattr("app.utils.path_utils.get_settings", mock_get_settings)


def test_validate_log_path_valid_in_var_log(_mock_settings: None) -> None:
    """Valid log paths in /var/log are accepted."""
    result = validate_log_path("/var/log/auth.log")
    assert result == "/var/log/auth.log"


def test_validate_log_path_valid_in_config_log(_mock_settings: None) -> None:
    """Valid log paths in /config/log are accepted."""
    result = validate_log_path("/config/log/app.log")
    assert result == "/config/log/app.log"


def test_validate_log_path_valid_with_subdirectory(_mock_settings: None) -> None:
    """Log paths in subdirectories of allowed paths are accepted."""
    result = validate_log_path("/var/log/syslog/auth.log")
    assert result == "/var/log/syslog/auth.log"


def test_validate_log_path_rejects_path_outside_allowed(_mock_settings: None) -> None:
    """Paths outside allowed directories are rejected."""
    with pytest.raises(ValueError) as exc_info:
        validate_log_path("/etc/passwd")
    error_msg = str(exc_info.value)
    assert "outside allowed directories" in error_msg
    assert "/etc/passwd" in error_msg


def test_validate_log_path_rejects_home_directory(_mock_settings: None) -> None:
    """Paths in home directories are rejected."""
    with pytest.raises(ValueError) as exc_info:
        validate_log_path("/home/user/app.log")
    error_msg = str(exc_info.value)
    assert "outside allowed directories" in error_msg


def test_validate_log_path_rejects_shadow_file(_mock_settings: None) -> None:
    """Paths to sensitive files like /etc/shadow are rejected."""
    with pytest.raises(ValueError) as exc_info:
        validate_log_path("/etc/shadow")
    error_msg = str(exc_info.value)
    assert "outside allowed directories" in error_msg


def test_validate_log_path_rejects_symlink_escape(monkeypatch: pytest.MonkeyPatch) -> None:
    """Symlinks that escape allowed directories are rejected."""
    with tempfile.TemporaryDirectory() as tmpdir:
        allowed_dir = Path(tmpdir) / "allowed"
        escape_dir = Path(tmpdir) / "escape"
        allowed_dir.mkdir()
        escape_dir.mkdir()

        symlink = allowed_dir / "escape_link"
        symlink.symlink_to(escape_dir)

        def mock_get_settings() -> Settings:
            return Settings(
                database_path=":memory:",
                fail2ban_socket="/tmp/fake.sock",
                fail2ban_config_dir="/tmp/fail2ban",
                session_secret="test-secret-key-do-not-use-in-production",
                allowed_log_dirs=[str(allowed_dir)],
            )

        monkeypatch.setattr("app.utils.path_utils.get_settings", mock_get_settings)

        # Accessing the symlink resolves it to the escape_dir, which is outside the allowed_dir.
        with pytest.raises(ValueError) as exc_info:
            validate_log_path(str(symlink / "evil.log"))
        error_msg = str(exc_info.value)
        assert "outside allowed directories" in error_msg


def test_validate_log_path_rejects_prefix_bypass(_mock_settings: None) -> None:
    """Paths that are similar to allowed paths but outside (e.g., /var/log_evil) are rejected."""
    with pytest.raises(ValueError) as exc_info:
        validate_log_path("/var/log_evil/somefile.log")
    error_msg = str(exc_info.value)
    assert "outside allowed directories" in error_msg


def test_validate_log_path_returns_unchanged_value(_mock_settings: None) -> None:
    """The returned value is exactly what was passed in."""
    path = "/var/log/app.log"
    result = validate_log_path(path)
    assert result is path or result == path


def test_validate_log_path_rejects_custom_allowed_dir_outside(
    _mock_settings: None, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Paths outside custom allowed directories are rejected."""

    def mock_get_settings() -> Settings:
        return Settings(
            database_path=":memory:",
            fail2ban_socket="/tmp/fake.sock",
            fail2ban_config_dir="/tmp/fail2ban",
            session_secret="test-secret-key-do-not-use-in-production",
            allowed_log_dirs=["/custom/logs"],
        )

    monkeypatch.setattr("app.utils.path_utils.get_settings", mock_get_settings)

    with pytest.raises(ValueError) as exc_info:
        validate_log_path("/var/log/app.log")
    error_msg = str(exc_info.value)
    assert "outside allowed directories" in error_msg
    assert "/custom/logs" in error_msg


def test_validate_log_path_accepts_custom_allowed_dir(monkeypatch: pytest.MonkeyPatch) -> None:
    """Paths within custom allowed directories are accepted."""

    def mock_get_settings() -> Settings:
        return Settings(
            database_path=":memory:",
            fail2ban_socket="/tmp/fake.sock",
            fail2ban_config_dir="/tmp/fail2ban",
            session_secret="test-secret-key-do-not-use-in-production",
            allowed_log_dirs=["/custom/logs"],
        )

    monkeypatch.setattr("app.utils.path_utils.get_settings", mock_get_settings)

    result = validate_log_path("/custom/logs/app.log")
    assert result == "/custom/logs/app.log"
