"""
Unit tests for filesystem utilities.

Tests the sanitize_folder_name function and related filesystem utilities.
"""

import os
import tempfile

import pytest

from src.server.utils.filesystem import (
    MAX_FOLDER_NAME_LENGTH,
    create_safe_folder,
    is_safe_path,
    sanitize_folder_name,
)


class TestSanitizeFolderName:
    """Test sanitize_folder_name function."""

    def test_simple_name(self):
        """Test sanitizing a simple name with no special characters."""
        assert sanitize_folder_name("Attack on Titan") == "Attack on Titan"

    def test_name_with_colon(self):
        """Test sanitizing name with colon."""
        result = sanitize_folder_name("Attack on Titan: Final Season")
        assert ":" not in result
        assert result == "Attack on Titan Final Season"

    def test_name_with_question_mark(self):
        """Test sanitizing name with question mark."""
        result = sanitize_folder_name("What If...?")
        assert "?" not in result
        # Trailing dots are stripped
        assert result == "What If"

    def test_name_with_multiple_special_chars(self):
        """Test sanitizing name with multiple special characters."""
        result = sanitize_folder_name('Test: "Episode" <1> | Part?')
        # All invalid chars should be removed
        assert ":" not in result
        assert '"' not in result
        assert "<" not in result
        assert ">" not in result
        assert "|" not in result
        assert "?" not in result

    def test_name_with_forward_slash(self):
        """Test sanitizing name with forward slash."""
        result = sanitize_folder_name("Attack/Titan")
        assert "/" not in result

    def test_name_with_backslash(self):
        """Test sanitizing name with backslash."""
        result = sanitize_folder_name("Attack\\Titan")
        assert "\\" not in result

    def test_unicode_characters_preserved(self):
        """Test that Unicode characters are preserved."""
        # Japanese title
        result = sanitize_folder_name("進撃の巨人")
        assert result == "進撃の巨人"

    def test_mixed_unicode_and_special(self):
        """Test mixed Unicode and special characters."""
        result = sanitize_folder_name("Re:ゼロ")
        assert ":" not in result
        assert "ゼロ" in result

    def test_leading_dots_removed(self):
        """Test that leading dots are removed."""
        result = sanitize_folder_name("...Hidden Folder")
        assert not result.startswith(".")

    def test_trailing_dots_removed(self):
        """Test that trailing dots are removed."""
        result = sanitize_folder_name("Folder Name...")
        assert not result.endswith(".")

    def test_leading_spaces_removed(self):
        """Test that leading spaces are removed."""
        result = sanitize_folder_name("   Attack on Titan")
        assert result == "Attack on Titan"

    def test_trailing_spaces_removed(self):
        """Test that trailing spaces are removed."""
        result = sanitize_folder_name("Attack on Titan   ")
        assert result == "Attack on Titan"

    def test_multiple_spaces_collapsed(self):
        """Test that multiple consecutive spaces are collapsed."""
        result = sanitize_folder_name("Attack    on     Titan")
        assert result == "Attack on Titan"

    def test_null_byte_removed(self):
        """Test that null byte is removed."""
        result = sanitize_folder_name("Attack\x00Titan")
        assert "\x00" not in result

    def test_newline_removed(self):
        """Test that newline is removed."""
        result = sanitize_folder_name("Attack\nTitan")
        assert "\n" not in result

    def test_tab_removed(self):
        """Test that tab is removed."""
        result = sanitize_folder_name("Attack\tTitan")
        assert "\t" not in result

    def test_none_raises_error(self):
        """Test that None raises ValueError."""
        with pytest.raises(ValueError, match="cannot be None"):
            sanitize_folder_name(None)

    def test_empty_string_raises_error(self):
        """Test that empty string raises ValueError."""
        with pytest.raises(ValueError, match="cannot be empty"):
            sanitize_folder_name("")

    def test_whitespace_only_raises_error(self):
        """Test that whitespace-only string raises ValueError."""
        with pytest.raises(ValueError, match="cannot be empty"):
            sanitize_folder_name("   ")

    def test_only_invalid_chars_raises_error(self):
        """Test that string with only invalid characters raises ValueError."""
        with pytest.raises(ValueError, match="only invalid characters"):
            sanitize_folder_name("???:::***")

    def test_max_length_truncation(self):
        """Test that long names are truncated."""
        long_name = "A" * 300
        result = sanitize_folder_name(long_name)
        assert len(result) <= MAX_FOLDER_NAME_LENGTH

    def test_max_length_custom(self):
        """Test custom max length."""
        result = sanitize_folder_name("Attack on Titan", max_length=10)
        assert len(result) <= 10

    def test_truncation_at_word_boundary(self):
        """Test that truncation happens at word boundary when possible."""
        result = sanitize_folder_name(
            "The Very Long Anime Title That Needs Truncation",
            max_length=25
        )
        # Should truncate at word boundary
        assert len(result) <= 25
        assert not result.endswith(" ")

    def test_custom_replacement_character(self):
        """Test custom replacement character."""
        result = sanitize_folder_name("Test:Name", replacement="_")
        assert ":" not in result
        assert "Test_Name" == result

    def test_asterisk_removed(self):
        """Test that asterisk is removed."""
        result = sanitize_folder_name("Attack*Titan")
        assert "*" not in result

    def test_pipe_removed(self):
        """Test that pipe is removed."""
        result = sanitize_folder_name("Attack|Titan")
        assert "|" not in result

    def test_real_anime_titles(self):
        """Test real anime titles with special characters."""
        # Test that invalid filesystem characters are removed
        # Note: semicolon is NOT an invalid filesystem character
        test_cases = [
            ("Re:Zero", ":"),  # colon should be removed
            ("86: Eighty-Six", ":"),  # colon should be removed
            ("Fate/Stay Night", "/"),  # slash should be removed
            ("Sword Art Online: Alicization", ":"),  # colon should be removed
            ("What If...?", "?"),  # question mark should be removed
        ]
        for input_name, forbidden_char in test_cases:
            result = sanitize_folder_name(input_name)
            assert forbidden_char not in result, f"'{forbidden_char}' should be removed from '{input_name}'"


class TestIsSafePath:
    """Test is_safe_path function."""

    def test_valid_subpath(self):
        """Test that valid subpath returns True."""
        assert is_safe_path("/anime", "/anime/Attack on Titan")

    def test_exact_match(self):
        """Test that exact match returns True."""
        assert is_safe_path("/anime", "/anime")

    def test_path_traversal_rejected(self):
        """Test that path traversal is rejected."""
        assert not is_safe_path("/anime", "/anime/../etc/passwd")

    def test_parent_directory_rejected(self):
        """Test that parent directory is rejected."""
        assert not is_safe_path("/anime/series", "/anime")

    def test_sibling_directory_rejected(self):
        """Test that sibling directory is rejected."""
        assert not is_safe_path("/anime", "/movies/film")

    def test_nested_subpath(self):
        """Test deeply nested valid subpath."""
        assert is_safe_path(
            "/anime",
            "/anime/Attack on Titan/Season 1/Episode 1"
        )


class TestCreateSafeFolder:
    """Test create_safe_folder function."""

    def test_creates_folder_with_sanitized_name(self):
        """Test that folder is created with sanitized name."""
        with tempfile.TemporaryDirectory() as tmpdir:
            path = create_safe_folder(tmpdir, "Attack: Titan?")
            assert os.path.isdir(path)
            assert ":" not in os.path.basename(path)
            assert "?" not in os.path.basename(path)

    def test_returns_full_path(self):
        """Test that full path is returned."""
        with tempfile.TemporaryDirectory() as tmpdir:
            path = create_safe_folder(tmpdir, "Test Folder")
            assert path.startswith(tmpdir)
            assert "Test Folder" in path

    def test_exist_ok_true(self):
        """Test that existing folder doesn't raise with exist_ok=True."""
        with tempfile.TemporaryDirectory() as tmpdir:
            # Create first time
            path1 = create_safe_folder(tmpdir, "Test Folder")
            # Create second time - should not raise
            path2 = create_safe_folder(tmpdir, "Test Folder", exist_ok=True)
            assert path1 == path2

    def test_rejects_path_traversal(self):
        """Test that path traversal is rejected after sanitization."""
        with tempfile.TemporaryDirectory() as tmpdir:
            # After sanitization, "../../../etc" becomes "etc" (dots removed)
            # So this test verifies the folder is created safely
            # The sanitization removes the path traversal attempt
            path = create_safe_folder(tmpdir, "../../../etc")
            # The folder should be created within tmpdir, not escape it
            assert is_safe_path(tmpdir, path)
            # Folder name should be "etc" after sanitization (dots stripped)
            assert os.path.basename(path) == "etc"


class TestSanitizeFolderNameEdgeCases:
    """Test edge cases for sanitize_folder_name."""

    def test_control_characters_removed(self):
        """Test that control characters are removed."""
        # ASCII control characters
        result = sanitize_folder_name("Test\x01\x02\x03Name")
        assert "\x01" not in result
        assert "\x02" not in result
        assert "\x03" not in result

    def test_carriage_return_removed(self):
        """Test that carriage return is removed."""
        result = sanitize_folder_name("Test\rName")
        assert "\r" not in result

    def test_unicode_normalization(self):
        """Test that Unicode is normalized."""
        # Composed vs decomposed forms
        result = sanitize_folder_name("café")
        # Should be normalized to NFC form
        assert result == "café"

    def test_emoji_handling(self):
        """Test handling of emoji characters."""
        result = sanitize_folder_name("Anime 🎬 Title")
        # Emoji should be preserved (valid Unicode)
        assert "🎬" in result or "Anime" in result

    def test_single_character_name(self):
        """Test single character name."""
        result = sanitize_folder_name("A")
        assert result == "A"

    def test_numbers_preserved(self):
        """Test that numbers are preserved."""
        result = sanitize_folder_name("86: Eighty-Six (2021)")
        assert "86" in result
        assert "2021" in result
