import asyncio
import copy
import re
import shutil
import tempfile
import uuid
from dataclasses import dataclass
from datetime import datetime, timedelta
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest
from app.features.ytdlp.utils import arg_converter
from app.library.Utils import (
    FileLogFormatter,
    calc_download_path,
    check_id,
    clean_item,
    decrypt_data,
    delete_dir,
    dt_delta,
    encrypt_data,
    get,
    get_file,
    get_file_sidecar,
    get_files,
    get_mime_type,
    get_possible_images,
    init_class,
    is_private_address,
    list_folders,
    load_cookies,
    load_modules,
    merge_dict,
    move_file,
    parse_tags,
    rename_file,
    str_to_dt,
    strip_newline,
    timed_lru_cache,
    validate_url,
    validate_uuid,
)
from app.routes.api.logs import _read_logfile, _tail_log


class TestTimedLruCache:
    """Test the timed_lru_cache decorator."""

    def test_timed_lru_cache_basic_functionality(self):
        """Test that timed_lru_cache caches function results."""
        call_count = 0

        @timed_lru_cache(ttl_seconds=60, max_size=10)
        def test_function(x):
            nonlocal call_count
            call_count += 1
            return x * 2

        # First call should execute the function
        result1 = test_function(5)
        assert result1 == 10
        assert call_count == 1

        # Second call with same argument should return cached result
        result2 = test_function(5)
        assert result2 == 10
        assert call_count == 1  # Should not increment

        # Different argument should execute the function again
        result3 = test_function(10)
        assert result3 == 20
        assert call_count == 2

    def test_timed_lru_cache_expiration(self):
        """Test that cache expires after TTL."""
        import time

        call_count = 0

        @timed_lru_cache(ttl_seconds=1, max_size=10)  # 1 second TTL
        def test_function(x):
            nonlocal call_count
            call_count += 1
            return x * 2

        # First call
        result1 = test_function(5)
        assert result1 == 10
        assert call_count == 1

        # Second call immediately should be cached
        result2 = test_function(5)
        assert result2 == 10
        assert call_count == 1

        # Wait for cache to expire
        time.sleep(1.1)

        # Call after expiration should execute function again
        result3 = test_function(5)
        assert result3 == 10
        assert call_count == 2

    def test_timed_lru_cache_methods_exposed(self):
        """Test that cache_clear and cache_info methods are exposed."""
        from app.library.Utils import timed_lru_cache

        @timed_lru_cache(ttl_seconds=60, max_size=10)
        def test_function(x):
            return x * 2

        assert hasattr(test_function, "cache_clear"), "Cached function should have cache_clear method"
        assert hasattr(test_function, "cache_info"), "Cached function should have cache_info method"

        # Call function to populate cache
        test_function(5)

        # Get cache info
        info = test_function.cache_info()
        assert info.hits == 0
        assert info.misses == 1

        # Call again to test hit
        test_function(5)
        info = test_function.cache_info()
        assert info.hits == 1
        assert info.misses == 1

        # Clear cache
        test_function.cache_clear()
        info = test_function.cache_info()
        assert info.hits == 0
        assert info.misses == 0

    def test_timed_lru_cache_max_size(self):
        """Test that cache respects max_size limit."""
        from app.library.Utils import timed_lru_cache

        @timed_lru_cache(ttl_seconds=60, max_size=2)
        def test_function(x):
            return x * 2

        # Fill cache to max size
        test_function(1)
        test_function(2)

        info = test_function.cache_info()
        assert info.misses == 2

        # Adding another item should not exceed max size
        test_function(3)
        info = test_function.cache_info()
        assert info.misses == 3

        # Test that earlier entries may be evicted
        test_function(1)  # This might be a cache miss due to LRU eviction


class TestAsyncTimedLruCache:
    """Test async functionality of timed_lru_cache decorator."""

    @pytest.mark.asyncio
    async def test_async_timed_lru_cache_basic(self):
        """Test basic async caching functionality."""
        from app.library.Utils import timed_lru_cache

        call_count = 0

        @timed_lru_cache(ttl_seconds=300, max_size=128)
        async def async_test_func(x):
            nonlocal call_count
            call_count += 1
            return x * 2

        # First call should execute the function
        result1 = await async_test_func(5)
        assert result1 == 10
        assert call_count == 1

        # Second call should use cached result
        result2 = await async_test_func(5)
        assert result2 == 10
        assert call_count == 1  # Should not increment

        # Different argument should execute function again
        result3 = await async_test_func(3)
        assert result3 == 6
        assert call_count == 2

    @pytest.mark.asyncio
    async def test_async_timed_lru_cache_expiry(self):
        """Test that async cache entries expire after TTL."""
        from app.library.Utils import timed_lru_cache

        call_count = 0

        @timed_lru_cache(ttl_seconds=0.1, max_size=128)  # 100ms TTL
        async def async_expire_func(x):
            nonlocal call_count
            call_count += 1
            return x * 3

        # First call
        result1 = await async_expire_func(2)
        assert result1 == 6
        assert call_count == 1

        # Immediate second call should use cache
        result2 = await async_expire_func(2)
        assert result2 == 6
        assert call_count == 1

        # Wait for cache to expire
        await asyncio.sleep(0.15)

        # Third call should execute function again due to expiry
        result3 = await async_expire_func(2)
        assert result3 == 6
        assert call_count == 2

    @pytest.mark.asyncio
    async def test_async_cache_methods(self):
        """Test that async cached functions expose cache management methods."""
        from app.library.Utils import timed_lru_cache

        @timed_lru_cache(ttl_seconds=300, max_size=128)
        async def async_method_test(x):
            return x + 1

        assert hasattr(async_method_test, "cache_clear"), "Async cached function should have cache_clear method"
        assert hasattr(async_method_test, "cache_info"), "Async cached function should have cache_info method"

        # Test cache_info
        info = async_method_test.cache_info()
        assert hasattr(info, "hits")
        assert hasattr(info, "misses")
        assert hasattr(info, "maxsize")
        assert hasattr(info, "currsize")
        assert info.maxsize == 128

        # Test cache_clear
        await async_method_test(1)
        async_method_test.cache_clear()
        info_after_clear = async_method_test.cache_info()
        assert info_after_clear.currsize == 0

    @pytest.mark.asyncio
    async def test_async_cache_max_size(self):
        """Test that async cache respects max_size parameter."""
        from app.library.Utils import timed_lru_cache

        @timed_lru_cache(ttl_seconds=300, max_size=2)
        async def async_limited_func(x):
            return x * 4

        # Fill cache beyond max_size
        result1 = await async_limited_func(1)
        result2 = await async_limited_func(2)
        result3 = await async_limited_func(3)

        assert result1 == 4, "async_limited_func(1) should return 4"
        assert result2 == 8, "async_limited_func(2) should return 8"
        assert result3 == 12, "async_limited_func(3) should return 12 (should evict oldest entry)"

        # Check cache size is limited
        info = async_limited_func.cache_info()
        assert info.currsize <= 2


class TestFileLogFormatter:
    """Test the FileLogFormatter class."""

    def test_file_log_formatter_creation(self):
        """Test that FileLogFormatter can be created."""
        formatter = FileLogFormatter()
        assert isinstance(formatter, FileLogFormatter)


class TestCalcDownloadPath:
    """Test the calc_download_path function."""

    def setup_method(self):
        """Set up test directory."""
        self.temp_dir = tempfile.mkdtemp()
        self.base_path = Path(self.temp_dir)

    def teardown_method(self):
        """Clean up after tests."""
        import shutil

        shutil.rmtree(self.temp_dir, ignore_errors=True)

    def test_calc_download_path_base_only(self):
        """Test calculating download path with only base path."""
        result = calc_download_path(str(self.base_path), create_path=False)
        assert result == str(self.base_path), "Should return base path when no folder is provided"

    def test_calc_download_path_with_folder(self):
        """Test calculating download path with folder."""
        folder = "test_folder"
        result = calc_download_path(str(self.base_path), folder, create_path=False)
        expected = str(self.base_path / folder)
        assert result == expected, "Should append folder to base path"

    def test_calc_download_path_creates_directory(self):
        """Test that the function creates directories when create_path=True."""
        folder = "new_folder"
        result = calc_download_path(str(self.base_path), folder, create_path=True)
        expected_path = self.base_path / folder
        assert result == str(expected_path), "Should return the new path"
        assert expected_path.exists(), "Directory should be created"

    def test_calc_download_path_path_object(self):
        """Test with Path object as input."""
        folder = "test_folder"
        result = calc_download_path(self.base_path, folder, create_path=False)
        expected = str(self.base_path / folder)
        assert result == expected, "Should handle Path object for base path"

    def test_calc_download_path_nested_folder(self):
        """Test with nested folder structure."""
        folder = "parent/child"
        result = calc_download_path(str(self.base_path), folder, create_path=True)
        expected_path = self.base_path / "parent" / "child"
        assert result == str(expected_path), "Should handle nested folder structure"
        assert expected_path.exists(), "Nested directories should be created"

    def test_calc_download_path_none_folder(self):
        """Test with None folder."""
        result = calc_download_path(str(self.base_path), None, create_path=False)
        assert result == str(self.base_path), "Should return base path when folder is None"

    def test_calc_download_path_removes_leading_slash(self):
        """Test that leading slash is removed from folder."""
        folder = "/test_folder"
        result = calc_download_path(str(self.base_path), folder, create_path=False)
        expected = str(self.base_path / "test_folder")
        assert result == expected, "Should remove leading slash from folder"

    def test_calc_download_path_path_traversal_dotdot(self):
        """Test path traversal prevention with .. sequences."""
        folder = "../outside"
        with pytest.raises(Exception, match="must resolve inside the base download folder"):
            calc_download_path(str(self.base_path), folder, create_path=False)

    def test_calc_download_path_path_traversal_nested_dotdot(self):
        """Test path traversal prevention with nested .. sequences."""
        folder = "safe/../../outside"
        with pytest.raises(Exception, match="must resolve inside the base download folder"):
            calc_download_path(str(self.base_path), folder, create_path=False)

    def test_calc_download_path_path_traversal_multiple_dotdot(self):
        """Test path traversal prevention with multiple .. sequences."""
        folder = "../../../etc/passwd"
        with pytest.raises(Exception, match="must resolve inside the base download folder"):
            calc_download_path(str(self.base_path), folder, create_path=False)

    def test_calc_download_path_path_traversal_absolute_path(self):
        """Test that absolute paths are made safe by removing leading slash."""
        folder = "/etc/passwd"
        result = calc_download_path(str(self.base_path), folder, create_path=False)
        expected = str(self.base_path / "etc/passwd")
        assert result == expected, "Should remove leading slash and treat as relative path"

    def test_calc_download_path_path_traversal_absolute_with_dotdot(self):
        """Test path traversal with absolute path containing .. sequences."""
        folder = "/../../../etc/passwd"
        with pytest.raises(Exception, match="must resolve inside the base download folder"):
            calc_download_path(str(self.base_path), folder, create_path=False)

    def test_calc_download_path_path_traversal_mixed(self):
        """Test path traversal prevention with mixed safe/unsafe paths."""
        folder = "safe/../../../unsafe"
        with pytest.raises(Exception, match="must resolve inside the base download folder"):
            calc_download_path(str(self.base_path), folder, create_path=False)

    def test_calc_download_path_path_traversal_url_encoded(self):
        """Test path traversal prevention with URL encoded sequences."""
        folder = "safe%2F..%2F..%2Funsafe"  # safe/../unsafe encoded
        # This should be handled at a higher level, but let's test it anyway
        result = calc_download_path(str(self.base_path), folder, create_path=False)
        expected = str(self.base_path / folder)  # Should be treated as literal filename
        assert result == expected, "URL encoded sequences should be treated as literal"

    def test_calc_download_path_safe_nested_paths(self):
        """Test that legitimate nested paths work correctly."""
        folder = "videos/2024/january"
        result = calc_download_path(str(self.base_path), folder, create_path=True)
        expected_path = self.base_path / "videos" / "2024" / "january"
        assert result == str(expected_path), "Should handle legitimate nested paths"
        assert expected_path.exists(), "Nested directories should be created"

    def test_calc_download_path_safe_dotfiles(self):
        """Test that paths with legitimate dot files work."""
        folder = ".hidden/folder"
        result = calc_download_path(str(self.base_path), folder, create_path=True)
        expected_path = self.base_path / ".hidden" / "folder"
        assert result == str(expected_path), "Should handle dot files correctly"
        assert expected_path.exists(), "Hidden directories should be created"

    def test_calc_download_path_empty_folder(self):
        """Test with empty string folder."""
        folder = ""
        result = calc_download_path(str(self.base_path), folder, create_path=False)
        assert result == str(self.base_path), "Should return base path for empty folder"

    def test_calc_download_path_whitespace_folder(self):
        """Test with whitespace-only folder."""
        folder = "   "
        result = calc_download_path(str(self.base_path), folder, create_path=True)
        expected_path = self.base_path / "   "
        assert result == str(expected_path), "Should handle whitespace folder names"

    def test_calc_download_path_unicode_folder(self):
        """Test with Unicode characters in folder name."""
        folder = "测试文件夹/русский/العربية"
        result = calc_download_path(str(self.base_path), folder, create_path=True)
        expected_path = self.base_path / "测试文件夹" / "русский" / "العربية"
        assert result == str(expected_path), "Should handle Unicode folder names"
        assert expected_path.exists(), "Unicode directories should be created"

    def test_calc_download_path_special_characters(self):
        """Test with special characters in folder name."""
        folder = "folder-with_special.chars(123)"
        result = calc_download_path(str(self.base_path), folder, create_path=True)
        expected_path = self.base_path / folder
        assert result == str(expected_path), "Should handle special characters"
        assert expected_path.exists(), "Directory with special chars should be created"

    def test_calc_download_path_null_byte_attack(self):
        """Test that null bytes are prevented."""
        folder = "folder\x00../../../etc/passwd"
        # Any exception is acceptable for null byte attacks
        with pytest.raises((ValueError, Exception)):
            calc_download_path(str(self.base_path), folder, create_path=False)

    def test_calc_download_path_null_byte_at_end(self):
        """Test that null bytes at end are prevented."""
        folder = "../../../etc/passwd\x00"
        # Any exception is acceptable for null byte attacks
        with pytest.raises((ValueError, Exception)):
            calc_download_path(str(self.base_path), folder, create_path=False)

    def test_calc_download_path_newline_attack(self):
        """Test that newlines in path traversal are prevented."""
        folder = "folder\n../../../etc/passwd"
        with pytest.raises(Exception, match="must resolve inside the base download folder"):
            calc_download_path(str(self.base_path), folder, create_path=False)

    def test_calc_download_path_carriage_return_attack(self):
        """Test that carriage returns in path traversal are prevented."""
        folder = "folder\r../../../etc/passwd"
        with pytest.raises(Exception, match="must resolve inside the base download folder"):
            calc_download_path(str(self.base_path), folder, create_path=False)

    def test_calc_download_path_tab_attack(self):
        """Test that tabs in path traversal are prevented."""
        folder = "folder\t../../../etc/passwd"
        with pytest.raises(Exception, match="must resolve inside the base download folder"):
            calc_download_path(str(self.base_path), folder, create_path=False)

    def test_calc_download_path_vertical_tab_attack(self):
        """Test that vertical tabs in path traversal are prevented."""
        folder = "folder\x0b../../../etc/passwd"
        with pytest.raises(Exception, match="must resolve inside the base download folder"):
            calc_download_path(str(self.base_path), folder, create_path=False)

    def test_calc_download_path_form_feed_attack(self):
        """Test that form feeds in path traversal are prevented."""
        folder = "folder\x0c../../../etc/passwd"
        with pytest.raises(Exception, match="must resolve inside the base download folder"):
            calc_download_path(str(self.base_path), folder, create_path=False)

    def test_calc_download_path_url_encoded_safe(self):
        """Test that URL encoded sequences are treated as literals (safe)."""
        folder = "folder%00../../../etc/passwd"  # %00 is URL encoded null
        with pytest.raises(Exception, match="must resolve inside the base download folder"):
            calc_download_path(str(self.base_path), folder, create_path=False)

    def test_calc_download_path_url_encoded_traversal_safe(self):
        """Test that URL encoded path traversal is treated as literal (safe)."""
        folder = "folder..%2F..%2F..%2Fetc%2Fpasswd"  # ..%2F = ../ encoded
        result = calc_download_path(str(self.base_path), folder, create_path=False)
        expected = str(self.base_path / folder)  # Should be treated as literal filename
        assert result == expected, "URL encoded sequences should be treated as literal filename"

    def test_calc_download_path_backslash_attack(self):
        """Test that backslashes in path traversal are handled correctly."""
        folder = "folder\\..\\..\\..\\etc\\passwd"
        # On Unix systems, backslashes are treated as literal characters in filenames
        result = calc_download_path(str(self.base_path), folder, create_path=False)
        expected = str(self.base_path / folder)
        assert result == expected, "Backslashes should be treated as literal characters on Unix"

    def test_calc_download_path_mixed_separators_attack(self):
        """Test path traversal with mixed separators."""
        folder = "folder/../../../etc/passwd"
        with pytest.raises(Exception, match="must resolve inside the base download folder"):
            calc_download_path(str(self.base_path), folder, create_path=False)

    def test_calc_download_path_partial_match_attack(self):
        """
        Test specific prefix matching vulnerability.
        Base: /tmp/test
        Target: /tmp/test_suffix (starts with base, but is a sibling)
        This tests that the relative_to() check catches what startswith() alone would miss.
        """
        # Create a sibling directory that starts with the base path name
        sibling_dir = Path(str(self.temp_dir) + "_suffix")
        sibling_dir.mkdir(exist_ok=True)

        # Try to access the sibling directory
        folder = f"../{sibling_dir.name}"

        with pytest.raises(Exception, match="must resolve inside the base download folder"):
            calc_download_path(str(self.base_path), folder, create_path=False)

        # Clean up
        shutil.rmtree(sibling_dir, ignore_errors=True)

    def test_calc_download_path_symlink_attack_outside(self):
        """Test that symlinks pointing outside base directory are blocked."""
        # Create a symlink pointing outside the base directory
        outside_dir = Path(self.temp_dir).parent / "outside_target"
        outside_dir.mkdir(exist_ok=True)

        symlink_path = self.base_path / "evil_symlink"
        try:
            symlink_path.symlink_to(outside_dir, target_is_directory=True)

            # Try to use the symlink
            with pytest.raises(Exception, match="must resolve inside the base download folder"):
                calc_download_path(str(self.base_path), "evil_symlink", create_path=False)
        finally:
            # Clean up
            if symlink_path.exists():
                symlink_path.unlink()
            shutil.rmtree(outside_dir, ignore_errors=True)

    def test_calc_download_path_symlink_attack_with_traversal(self):
        """Test symlink combined with path traversal."""
        # Create a directory outside base
        outside_dir = Path(self.temp_dir).parent / "target_dir"
        outside_dir.mkdir(exist_ok=True)

        # Create a symlink inside base pointing outside
        safe_dir = self.base_path / "safe"
        safe_dir.mkdir(exist_ok=True)

        symlink_path = safe_dir / "link_to_outside"
        try:
            symlink_path.symlink_to(outside_dir, target_is_directory=True)

            # Try to traverse through the symlink
            with pytest.raises(Exception, match="must resolve inside the base download folder"):
                calc_download_path(str(self.base_path), "safe/link_to_outside", create_path=False)
        finally:
            # Clean up
            if symlink_path.exists():
                symlink_path.unlink()
            shutil.rmtree(safe_dir, ignore_errors=True)
            shutil.rmtree(outside_dir, ignore_errors=True)

    def test_calc_download_path_symlink_safe_internal(self):
        """Test that symlinks pointing inside base directory are allowed."""
        # Create target directory inside base
        target_dir = self.base_path / "target"
        target_dir.mkdir(exist_ok=True)

        # Create a symlink inside base pointing to another location inside base
        symlink_path = self.base_path / "link_to_target"
        try:
            symlink_path.symlink_to(target_dir, target_is_directory=True)

            # This should succeed since symlink resolves inside base
            result = calc_download_path(str(self.base_path), "link_to_target", create_path=False)
            assert str(target_dir) == result, "Internal symlinks should be allowed"
        finally:
            # Clean up
            if symlink_path.exists():
                symlink_path.unlink()
            shutil.rmtree(target_dir, ignore_errors=True)

    def test_calc_download_path_extremely_long_path(self):
        """Test handling of extremely long paths."""
        # Create a very long folder name (most filesystems limit to 255 chars per component)
        long_component = "a" * 300

        # This should raise an error during path creation or validation (OSError or ValueError)
        with pytest.raises((OSError, ValueError)):
            calc_download_path(str(self.base_path), long_component, create_path=True)

    def test_calc_download_path_many_nested_levels(self):
        """Test deeply nested directory structure."""
        # Create a path with many nested levels
        deep_path = "/".join([f"level{i}" for i in range(100)])

        # This should work but might be slow
        result = calc_download_path(str(self.base_path), deep_path, create_path=False)
        expected = str(self.base_path / deep_path)
        assert result == expected, "Should handle deeply nested paths"

    def test_calc_download_path_directory_with_spaces(self):
        """Test paths with multiple spaces and special spacing."""
        folder = "folder with   multiple    spaces"
        result = calc_download_path(str(self.base_path), folder, create_path=True)
        expected_path = self.base_path / folder
        assert result == str(expected_path), "Should handle spaces correctly"
        assert expected_path.exists(), "Directory with spaces should be created"


class TestMergeDict:
    """Test the merge_dict function."""

    def test_merge_dict_basic(self):
        """Test basic dictionary merging."""
        source = {"a": 1, "b": 2}
        destination = {"c": 3, "d": 4}
        result = merge_dict(source, destination)
        expected = {"a": 1, "b": 2, "c": 3, "d": 4}
        assert result == expected

    def test_merge_dict_overwrites(self):
        """Test that source values overwrite destination values."""
        source = {"a": 1, "b": 2}
        destination = {"b": 99, "c": 3}
        result = merge_dict(source, destination)
        expected = {"a": 1, "b": 2, "c": 3}
        assert result == expected

    def test_merge_dict_nested(self):
        """Test merging nested dictionaries."""
        source = {"nested": {"a": 1}}
        destination = {"nested": {"b": 2}, "other": 3}
        result = merge_dict(source, destination)
        assert "nested" in result, "Should merge nested dictionaries"
        assert "other" in result, "Should preserve other keys"

    def test_merge_dict_empty_source(self):
        """Test merging with empty source."""
        source = {}
        destination = {"a": 1, "b": 2}
        result = merge_dict(source, destination)
        assert result == destination

    def test_merge_dict_empty_destination(self):
        """Test merging with empty destination."""
        source = {"a": 1, "b": 2}
        destination = {}
        result = merge_dict(source, destination)
        assert result == source

    def test_merge_dict_both_empty(self):
        """Test merging two empty dictionaries."""
        result = merge_dict({}, {})
        assert result == {}

    # Parameter Pollution Security Tests

    def test_merge_dict_blocks_class_pollution(self):
        """Test that __class__ attribute pollution is blocked."""
        source = {"__class__": "malicious_class", "safe": "value"}
        destination = {"existing": "data"}
        result = merge_dict(source, destination)

        assert "__class__" not in result, "__class__ attribute pollution should be blocked"
        assert result["safe"] == "value", "Safe values should be preserved"
        assert result["existing"] == "data", "Existing data should be preserved"

    def test_merge_dict_blocks_dict_pollution(self):
        """Test that __dict__ attribute pollution is blocked."""
        source = {"__dict__": {"injected": "payload"}, "normal": "data"}
        destination = {"target": "value"}
        result = merge_dict(source, destination)

        assert "__dict__" not in result, "__dict__ should be filtered out"
        assert result["normal"] == "data", "Normal data should be preserved"
        assert result["target"] == "value", "Target data should be preserved"

    def test_merge_dict_blocks_globals_pollution(self):
        """Test that __globals__ attribute pollution is blocked."""
        source = {"__globals__": {"malicious": "code"}, "data": "safe"}
        destination = {"existing": "value"}
        result = merge_dict(source, destination)

        assert "__globals__" not in result, "__globals__ should be filtered out"
        assert result["data"] == "safe", "Safe data should be preserved"

    def test_merge_dict_blocks_builtins_pollution(self):
        """Test that __builtins__ attribute pollution is blocked."""
        source = {"__builtins__": {"eval": "dangerous"}, "normal": "value"}
        destination = {"target": "data"}
        result = merge_dict(source, destination)

        assert "__builtins__" not in result, "__builtins__ should be filtered out"
        assert result["normal"] == "value", "Normal values should be preserved"

    def test_merge_dict_blocks_multiple_dunder_pollution(self):
        """Test that multiple dangerous dunder attributes are blocked."""
        source = {
            "__class__": "malicious",
            "__dict__": {"bad": "data"},
            "__globals__": {"evil": "code"},
            "__builtins__": {"dangerous": "function"},
            "safe_key": "safe_value",
        }
        destination = {"existing": "data"}
        result = merge_dict(source, destination)

        # All dangerous attributes should be filtered out
        dangerous_keys = ["__class__", "__dict__", "__globals__", "__builtins__"]
        for key in dangerous_keys:
            assert key not in result, f"{key} should be filtered out (all dangerous attributes)"

        assert result["safe_key"] == "safe_value", "Safe data should be preserved"
        assert result["existing"] == "data", "Existing data should be preserved"

    def test_merge_dict_nested_dunder_pollution(self):
        """Test that nested dangerous attributes are handled correctly."""
        source = {"nested": {"__class__": "malicious_nested", "safe_nested": "value"}, "normal": "data"}
        destination = {"nested": {"existing_nested": "original"}}
        result = merge_dict(source, destination)

        assert "__class__" not in result["nested"], "Nested dangerous attributes should be filtered out"
        assert result["nested"]["safe_nested"] == "value", "Safe nested data should be preserved"
        assert result["nested"]["existing_nested"] == "original", "Existing nested data should be preserved"

    def test_merge_dict_prototype_pollution_attempt(self):
        """Test protection against prototype pollution attempts."""
        source = {"__proto__": {"polluted": True}, "constructor": {"prototype": {"polluted": True}}, "safe": "data"}
        destination = {"existing": "value"}
        result = merge_dict(source, destination)

        assert result["safe"] == "data", (
            "Function filters Python-specific dangerous attributes, not JS ones like __proto__"
        )
        assert result["existing"] == "value", "Existing data should be preserved"

    def test_merge_dict_special_method_pollution(self):
        """Test with various Python special methods."""
        source = {
            "__init__": "malicious_init",
            "__new__": "malicious_new",
            "__call__": "malicious_call",
            "__getattr__": "malicious_getattr",
            "__setattr__": "malicious_setattr",
            "safe": "value",
        }
        destination = {"target": "data"}
        result = merge_dict(source, destination)

        assert result["safe"] == "value", (
            "Safe data should be preserved (special methods not in filter list, documents current behavior)"
        )
        assert result["target"] == "data", "Target data should be preserved"

    def test_merge_dict_list_pollution_safe(self):
        """Test that list merging doesn't allow dangerous manipulation."""
        source = {"items": ["new1", "new2"]}
        destination = {"items": ["old1", "old2"]}
        result = merge_dict(source, destination)

        assert result["items"] == ["old1", "old2", "new1", "new2"], (
            "Lists should be concatenated safely (destination + source)"
        )

    def test_merge_dict_deep_nested_pollution(self):
        """Test with deeply nested dangerous attributes."""
        source = {
            "level1": {
                "level2": {
                    "__class__": "deep_malicious",
                    "level3": {"__globals__": "very_deep_malicious"},
                    "safe_deep": "value",
                }
            }
        }
        destination = {"level1": {"level2": {"existing": "data"}}}
        result = merge_dict(source, destination)

        assert "__class__" not in result["level1"]["level2"], (
            "Function should properly filter all dangerous keys recursively (deep __class__)"
        )
        assert "__globals__" not in result["level1"]["level2"]["level3"], "Function should filter very deep __globals__"

        assert result["level1"]["level2"]["safe_deep"] == "value", "Safe nested data should be preserved"
        assert result["level1"]["level2"]["existing"] == "data", "Existing nested data should be preserved"

    def test_merge_dict_type_validation(self):
        """Test that non-dict parameters are properly rejected."""
        # Test with non-dict source
        with pytest.raises(TypeError, match="Both source and destination must be dictionaries"):
            merge_dict("not_a_dict", {"key": "value"})

        # Test with non-dict destination
        with pytest.raises(TypeError, match="Both source and destination must be dictionaries"):
            merge_dict({"key": "value"}, "not_a_dict")

        # Test with both non-dict
        with pytest.raises(TypeError, match="Both source and destination must be dictionaries"):
            merge_dict("not_a_dict", ["also_not_dict"])

    def test_merge_dict_immutability(self):
        """Test that original dictionaries are not modified (immutability)."""
        original_source = {"a": 1, "nested": {"b": 2}}
        original_destination = {"c": 3, "nested": {"d": 4}}

        # Make copies to compare later
        source_copy = copy.deepcopy(original_source)
        destination_copy = copy.deepcopy(original_destination)

        result = merge_dict(original_source, original_destination)

        assert original_source == source_copy, "Original source dictionary should be unchanged (immutability)"
        assert original_destination == destination_copy, (
            "Original destination dictionary should be unchanged (immutability)"
        )

        assert result != original_source, "Result should be different from source original"
        assert result != original_destination, "Result should be different from destination original"

    def test_merge_dict_custom_max_depth(self):
        """Test custom max_depth parameter."""
        # Create a deep nested structure
        deep_source = {}
        current = deep_source
        for _ in range(10):
            current["level"] = {}
            current = current["level"]
        current["data"] = "deep_value"

        # Test with default max_depth (50) - should work
        result = merge_dict(deep_source, {})
        assert self._get_nested_value(result, ["level"] * 10 + ["data"]) == "deep_value"

        # Test with custom max_depth (5) - should raise RecursionError
        with pytest.raises(RecursionError, match="Recursion depth limit exceeded \\(5\\)"):
            merge_dict(deep_source, {}, max_depth=5)

        # Test with higher max_depth (20) - should work
        result = merge_dict(deep_source, {}, max_depth=20)
        assert self._get_nested_value(result, ["level"] * 10 + ["data"]) == "deep_value"

    def test_merge_dict_custom_max_list_size(self):
        """Test custom max_list_size parameter."""
        large_list = list(range(5000))
        source = {"data": large_list}

        # Test with default max_list_size (10000) - should preserve full list
        result = merge_dict(source, {})
        assert len(result["data"]) == 5000
        assert result["data"] == large_list

        # Test with custom max_list_size (1000) - should truncate
        result = merge_dict(source, {}, max_list_size=1000)
        assert len(result["data"]) == 1000
        assert result["data"] == large_list[:1000]

        # Test with higher max_list_size (10000) - should preserve full list
        result = merge_dict(source, {}, max_list_size=10000)
        assert len(result["data"]) == 5000
        assert result["data"] == large_list

    def test_merge_dict_list_merging_with_size_limits(self):
        """Test list merging respects size limits."""
        source = {"items": list(range(3000))}
        destination = {"items": list(range(2000, 5000))}  # 3000 items

        result = merge_dict(source, destination, max_list_size=4000)

        assert len(result["items"]) == 4000, (
            "Total would be 6000 items, but limit is 4000: destination (3000) + truncated source (1000)"
        )

        assert result["items"][:3000] == list(range(2000, 5000)), "First 3000 items should be from destination"
        assert result["items"][3000:] == list(range(1000)), "Next 1000 items should be from source (truncated)"

    def test_merge_dict_nested_with_limits(self):
        """Test nested merging with both depth and size limits."""
        # Create nested structure that exceeds depth limit
        deep_source = {"level1": {"level2": {"level3": {"level4": {"data": "deep"}}}}}

        # Create structure with large list
        list_source = {"level1": {"large_data": list(range(2000))}}

        destination = {"level1": {"existing": "data"}}

        # Test with restrictive limits
        result = merge_dict(list_source, destination, max_depth=10, max_list_size=1000)

        assert result["level1"]["existing"] == "data"
        assert len(result["level1"]["large_data"]) == 1000
        assert result["level1"]["large_data"] == list(range(1000))

        # Test depth limit exceeded
        with pytest.raises(RecursionError):
            merge_dict(deep_source, destination, max_depth=3)

    def test_merge_dict_zero_limits(self):
        """Test behavior with zero limits."""
        source = {"data": [1, 2, 3]}
        destination = {"existing": "value"}

        # Zero max_list_size should result in empty lists
        result = merge_dict(source, destination, max_list_size=0)
        assert result["existing"] == "value"
        assert result["data"] == []  # Truncated to empty

        # Zero max_depth should fail immediately on any nesting
        with pytest.raises(RecursionError):
            merge_dict({"nested": {"data": "value"}}, {}, max_depth=0)

    def test_merge_dict_extreme_limits(self):
        """Test with very high limits."""
        # Create moderately nested structure
        source = {"a": {"b": {"c": {"data": "nested"}}}}

        # Very high limits should work normally
        result = merge_dict(source, {}, max_depth=10000, max_list_size=1000000)
        assert result["a"]["b"]["c"]["data"] == "nested"

    def test_merge_dict_limits_with_circular_reference_protection(self):
        """Test that limits work together with circular reference protection."""
        source = {"data": {}}
        source["data"]["circular"] = source  # Create circular reference

        # Should fail with ValueError (circular reference) before hitting depth limit
        with pytest.raises(ValueError, match="Circular reference detected"):
            merge_dict(source, {}, max_depth=100)

    def test_merge_dict_backward_compatibility(self):
        """Test that existing code works without specifying new parameters."""
        source = {"a": 1, "nested": {"b": 2}}
        destination = {"c": 3, "nested": {"d": 4}}

        # Should work exactly as before with default parameters
        result = merge_dict(source, destination)

        assert result["a"] == 1
        assert result["c"] == 3
        assert result["nested"]["b"] == 2
        assert result["nested"]["d"] == 4

    def _get_nested_value(self, data: dict, keys: list):
        """Helper to get value from deeply nested dict."""
        current = data
        for key in keys:
            current = current[key]
        return current


class TestIsPrivateAddress:
    """Test the is_private_address function."""

    def test_is_private_address_localhost(self):
        """Test localhost addresses."""
        assert is_private_address("localhost") is True
        assert is_private_address("127.0.0.1") is True

    def test_is_private_address_private_ranges(self):
        """Test private IP ranges."""
        assert is_private_address("192.168.1.1") is True
        assert is_private_address("10.0.0.1") is True
        assert is_private_address("172.16.0.1") is True

    def test_is_private_address_public(self):
        """Test public addresses."""
        assert is_private_address("8.8.8.8") is False
        assert is_private_address("google.com") is False
        assert is_private_address("1.1.1.1") is False


class TestDeleteDir:
    """Test the delete_dir function."""

    def setup_method(self):
        """Set up test directory."""
        self.temp_dir = tempfile.mkdtemp()
        self.test_dir = Path(self.temp_dir) / "test_delete"
        self.test_dir.mkdir()
        (self.test_dir / "file.txt").write_text("test content")

    def teardown_method(self):
        """Clean up after tests."""
        import shutil

        shutil.rmtree(self.temp_dir, ignore_errors=True)

    def test_delete_dir_success(self):
        """Test successful directory deletion."""
        assert self.test_dir.exists()
        result = delete_dir(self.test_dir)
        assert result is True
        assert not self.test_dir.exists()

    def test_delete_dir_nonexistent(self):
        """Test deleting non-existent directory."""
        nonexistent = Path(self.temp_dir) / "nonexistent"
        result = delete_dir(nonexistent)
        assert result is False


class TestListFolders:
    """Test the list_folders function."""

    def setup_method(self):
        """Set up test directory structure."""
        self.temp_dir = tempfile.mkdtemp()
        self.base = Path(self.temp_dir)
        (self.base / "folder1").mkdir()
        (self.base / "folder2").mkdir()
        (self.base / "folder1" / "subfolder").mkdir()
        (self.base / "file.txt").write_text("test")

    def teardown_method(self):
        """Clean up after tests."""
        import shutil

        shutil.rmtree(self.temp_dir, ignore_errors=True)

    def test_list_folders_depth_0(self):
        """Test listing folders with depth 0."""
        result = list_folders(self.base, self.base, 0)
        expected = ["folder1", "folder2"]
        assert sorted(result) == sorted(expected)

    def test_list_folders_depth_1(self):
        """Test listing folders with depth 1."""
        result = list_folders(self.base, self.base, 1)
        expected = ["folder1", "folder2", "folder1/subfolder"]
        assert sorted(result) == sorted(expected)

    def test_list_folders_depth_2(self):
        """Test listing folders with a depth limit."""
        result = list_folders(self.base, self.base, 2)
        expected = ["folder1", "folder2", "folder1/subfolder"]
        assert sorted(result) == sorted(expected)


class TestEncryptDecrypt:
    """Test encryption and decryption functions."""

    def test_encrypt_decrypt_data(self):
        """Test that data can be encrypted and decrypted."""
        key = b"test_key_12345678901234567890123"  # Exactly 32 bytes for AES
        data = "test data"

        encrypted: str = encrypt_data(data, key)
        assert encrypted != data
        assert isinstance(encrypted, str)

        decrypted: str = decrypt_data(encrypted, key)
        assert decrypted == data

    def test_encrypt_empty_string(self):
        """Test encrypting empty string."""
        key = b"test_key_12345678901234567890123"  # Exactly 32 bytes
        data: str = ""

        encrypted: str = encrypt_data(data, key)
        decrypted: str = decrypt_data(encrypted, key)
        assert decrypted == data


class TestValidateUuid:
    """Test UUID validation function."""

    def test_valid_uuid4(self):
        """Test with valid UUID4."""
        test_uuid = str(uuid.uuid4())
        assert validate_uuid(test_uuid, 4) is True

    def test_valid_uuid1(self):
        """Test with valid UUID1."""
        test_uuid = str(uuid.uuid1())
        assert validate_uuid(test_uuid, 1) is True

    def test_invalid_uuid_string(self):
        """Test with invalid UUID string."""
        assert validate_uuid("invalid-uuid", 4) is False

    def test_empty_string(self):
        """Test with empty string."""
        assert validate_uuid("", 4) is False

    def test_wrong_version(self):
        """Test UUID4 against version 1 check."""
        test_uuid = str(uuid.uuid4())
        # Version check is not strict in this function - it may return True for any valid UUID
        result = validate_uuid(test_uuid, 1)
        assert isinstance(result, bool)  # Just check it returns a boolean


class TestStripNewline:
    """Test string newline stripping function."""

    def test_strip_newline_basic(self):
        """Test stripping newlines from string."""
        text = "line1\nline2\r\nline3\r"
        result = strip_newline(text)
        assert result == "line1 line2 line3"  # Function replaces with spaces, not removes

    def test_strip_newline_no_newlines(self):
        """Test string with no newlines."""
        text = "no newlines here"
        result = strip_newline(text)
        assert result == text

    def test_strip_newline_empty(self):
        """Test empty string."""
        result = strip_newline("")
        assert result == ""

    def test_strip_newline_only_newlines(self):
        """Test string with only newlines."""
        text = "\n\r\n\r"
        result = strip_newline(text)
        assert result == ""


class TestDtDelta:
    """Test timedelta formatting function."""

    def test_dt_delta_seconds(self):
        """Test formatting seconds."""
        delta = timedelta(seconds=30)
        result = dt_delta(delta)
        assert "30" in result
        assert "s" in result

    def test_dt_delta_minutes(self):
        """Test formatting minutes."""
        delta = timedelta(minutes=5)
        result = dt_delta(delta)
        assert "5" in result
        assert "m" in result

    def test_dt_delta_hours(self):
        """Test formatting hours."""
        delta = timedelta(hours=2)
        result = dt_delta(delta)
        assert "2" in result
        assert "h" in result

    def test_dt_delta_days(self):
        """Test formatting days."""
        delta = timedelta(days=3)
        result = dt_delta(delta)
        assert "3" in result
        assert "d" in result

    def test_dt_delta_complex(self):
        """Test formatting complex timedelta."""
        delta = timedelta(days=1, hours=2, minutes=30, seconds=45)
        result = dt_delta(delta)
        assert isinstance(result, str)
        assert len(result) > 0


class TestParseTags:
    """Test tag parsing function."""

    def test_parse_tags_simple(self):
        """Test parsing simple tags."""
        text = "Hello [tag1] world [tag2:value]"
        result_text, tags = parse_tags(text)
        assert "Hello" in result_text
        assert "world" in result_text
        assert isinstance(tags, dict)

    def test_parse_tags_no_tags(self):
        """Test text with no tags."""
        text = "Hello world"
        result_text, tags = parse_tags(text)
        assert result_text == text
        assert tags == {}

    def test_parse_tags_empty(self):
        """Test empty string."""
        result_text, tags = parse_tags("")
        assert result_text == ""
        assert tags == {}


class TestCleanItem:
    """Test item cleaning function."""

    def test_clean_item_basic(self):
        """Test cleaning item with specified keys."""
        item = {"key1": "value1", "key2": "value2", "key3": "value3"}
        keys = ["key2"]
        cleaned_item, changed = clean_item(item, keys)

        assert "key1" in cleaned_item
        assert "key2" not in cleaned_item
        assert "key3" in cleaned_item
        assert changed is True

    def test_clean_item_no_change(self):
        """Test cleaning item with non-existent keys."""
        item = {"key1": "value1", "key2": "value2"}
        keys = ["nonexistent"]
        cleaned_item, changed = clean_item(item, keys)

        assert cleaned_item == item
        assert changed is False

    def test_clean_item_empty_keys(self):
        """Test cleaning item with empty keys list."""
        item = {"key1": "value1"}
        keys = []
        cleaned_item, changed = clean_item(item, keys)

        assert cleaned_item == item
        assert changed is False


class TestValidateUrl:
    """Test URL validation function."""

    def test_validate_url_basic(self):
        """Test basic URL validation functionality."""
        # Test without actual validation due to missing yarl dependency
        # Just check the function exists and handles the missing dependency gracefully
        try:
            result = validate_url("https://example.com")
            assert isinstance(result, bool)
        except ModuleNotFoundError:
            # Expected when yarl is not installed
            assert True


class TestGetFileSidecar:
    """Test file sidecar function."""

    def test_get_file_sidecar_with_files(self):
        """Test getting sidecar files when they exist."""
        with tempfile.TemporaryDirectory() as temp_dir:
            base_path = Path(temp_dir)
            video_file = base_path / "video.mp4"
            srt_file = base_path / "video.srt"
            nfo_file = base_path / "video.nfo"

            video_file.write_text("video content")
            srt_file.write_text("subtitle content")
            nfo_file.write_text("nfo content")

            result = get_file_sidecar(video_file)
            assert isinstance(result, dict)  # Returns dict, not list

    def test_get_file_sidecar_no_files(self):
        """Test getting sidecar files when none exist."""
        with tempfile.TemporaryDirectory() as temp_dir:
            base_path = Path(temp_dir)
            video_file = base_path / "video.mp4"
            video_file.write_text("video content")

            result = get_file_sidecar(video_file)
            assert isinstance(result, dict)  # Returns dict, not list


class TestCheckId:
    """Test the check_id function."""

    def setup_method(self):
        """Set up test files."""
        self.temp_dir = tempfile.mkdtemp()
        self.test_dir = Path(self.temp_dir)

    def teardown_method(self):
        """Clean up after tests."""
        import shutil

        shutil.rmtree(self.temp_dir, ignore_errors=True)

    def test_check_id_with_youtube_id(self):
        """Test check_id with a YouTube ID in filename."""
        # Create a file with YouTube ID
        test_file = self.test_dir / "video[test12345678].srt"
        test_file.write_text("subtitle content")

        # Create a corresponding video file
        video_file = self.test_dir / "video[test12345678].mp4"
        video_file.write_text("video content")

        result = check_id(test_file)
        assert isinstance(result, (bool, str))

    def test_check_id_no_id(self):
        """Test check_id with no ID in filename."""
        test_file = self.test_dir / "video.srt"
        test_file.write_text("subtitle content")

        result = check_id(test_file)
        assert result is False


class TestArgConverter:
    """Test the arg_converter function."""

    def test_arg_converter_basic(self):
        """Test basic arg_converter functionality."""
        try:
            result = arg_converter("--quiet --match-filters 'duration<2min' --download-archive archive.txt")
            assert isinstance(result, dict)
            assert result.get("quiet") is True, "quiet should be True"
            assert result.get("download_archive") == "archive.txt"
            assert "match_filter" in result, "match_filters should be in result"
        except (ModuleNotFoundError, AttributeError, ImportError):
            # Expected when yt_dlp is not available or differs
            assert True

    def test_arg_converter_empty_args(self):
        """Test arg_converter with empty args."""
        try:
            result = arg_converter("")
            assert isinstance(result, dict)
        except (ModuleNotFoundError, AttributeError, ImportError):
            assert True

    def test_arg_converter_replace_in_metadata(self):
        """Test arg_converter handles replace-in-metadata without assertions."""
        try:
            result = arg_converter("--replace-in-metadata title foo bar")
        except (ModuleNotFoundError, AttributeError, ImportError):
            assert True
            return

        postprocessors = result.get("postprocessors", [])
        assert postprocessors, "Expected metadata parser postprocessor to be present"

        metadata_pp = postprocessors[0]
        assert metadata_pp.get("key") == "MetadataParser"

        actions = metadata_pp.get("actions", [])
        assert actions, "Expected metadata parser to include actions"

        action_callable = actions[0][0]
        assert callable(action_callable)
        assert getattr(action_callable, "__name__", "") == "replacer"


class TestGetPossibleImages:
    """Test the get_possible_images function."""

    def setup_method(self):
        """Set up test directory with images."""
        self.temp_dir = tempfile.mkdtemp()
        self.test_dir = Path(self.temp_dir)

        # Create some test image files
        (self.test_dir / "poster.jpg").write_text("image")
        (self.test_dir / "thumbnail.png").write_text("image")

    def teardown_method(self):
        """Clean up after tests."""
        import shutil

        shutil.rmtree(self.temp_dir, ignore_errors=True)

    def test_get_possible_images(self):
        """Test getting possible images."""
        result = get_possible_images(str(self.test_dir))
        assert isinstance(result, list)

    def test_get_possible_images_empty_dir(self):
        """Test getting images from empty directory."""
        empty_dir = Path(self.temp_dir) / "empty"
        empty_dir.mkdir()

        result = get_possible_images(str(empty_dir))
        assert isinstance(result, list)


class TestGetMimeType:
    """Test the get_mime_type function."""

    def test_get_mime_type_mp4(self):
        """Test MIME type detection for MP4."""
        metadata = {"format_name": "mp4"}
        file_path = Path("test.mp4")

        result = get_mime_type(metadata, file_path)
        assert isinstance(result, str)
        assert "video" in result

    def test_get_mime_type_mkv(self):
        """Test MIME type detection for MKV."""
        metadata = {"format_name": "matroska"}
        file_path = Path("test.mkv")

        result = get_mime_type(metadata, file_path)
        assert isinstance(result, str)

    def test_get_mime_type_fallback(self):
        """Test MIME type fallback."""
        metadata = {}
        file_path = Path("test.unknown")

        result = get_mime_type(metadata, file_path)
        assert isinstance(result, str)


class TestGetFile:
    """Test the get_file function."""

    def setup_method(self):
        """Set up test files."""
        self.temp_dir = tempfile.mkdtemp()
        self.download_path = Path(self.temp_dir)

    def teardown_method(self):
        """Clean up after tests."""
        import shutil

        shutil.rmtree(self.temp_dir, ignore_errors=True)

    def test_get_file_exists(self):
        """Test getting existing file."""
        test_file = self.download_path / "test.txt"
        test_file.write_text("content")

        result_path, status_code = get_file(self.download_path, "test.txt")
        assert isinstance(result_path, Path)
        assert isinstance(status_code, int)

    def test_get_file_not_exists(self):
        """Test getting non-existent file."""
        result_path, status_code = get_file(self.download_path, "nonexistent.txt")
        assert isinstance(result_path, Path)
        assert status_code == 404


class TestGet:
    """Test the get function for nested data access."""

    def test_get_basic_dict(self):
        """Test getting value from dictionary."""
        data = {"key": "value"}
        result = get(data, "key")
        assert result == "value"

    def test_get_nested_dict(self):
        """Test getting nested value."""
        data = {"level1": {"level2": {"level3": "value"}}}
        result = get(data, "level1.level2.level3")
        assert result == "value"

    def test_get_with_default(self):
        """Test getting with default value."""
        data = {"key": "value"}
        result = get(data, "nonexistent", default="default")
        assert result == "default"

    def test_get_list_access(self):
        """Test getting from list."""
        data = ["item0", "item1", "item2"]
        result = get(data, 1)
        assert result == "item1"

    def test_get_empty_path(self):
        """Test with empty path."""
        data = {"key": "value"}
        result = get(data, None)
        assert result == data


class TestGetFiles:
    """Test the get_files function."""

    def setup_method(self):
        """Set up test directory structure."""
        self.temp_dir = tempfile.mkdtemp()
        self.base_path = Path(self.temp_dir)

        # Create test files and directories
        (self.base_path / "file1.txt").write_text("content")
        (self.base_path / "file2.txt").write_text("content")
        (self.base_path / "subdir").mkdir()
        (self.base_path / "subdir" / "file3.txt").write_text("content")

    def teardown_method(self):
        """Clean up after tests."""
        import shutil

        shutil.rmtree(self.temp_dir, ignore_errors=True)

    def test_get_files_root(self):
        """Test getting files from root directory."""
        result, total = get_files(self.base_path)
        assert isinstance(result, list)
        assert len(result) > 0

    def test_get_files_subdir(self):
        """Test getting files from subdirectory."""
        result, total = get_files(self.base_path, "subdir")
        assert isinstance(result, list)


class TestReadLogfile:
    """Test the read_logfile async function."""

    def setup_method(self):
        """Set up test log file."""
        self.temp_dir = tempfile.mkdtemp()
        self.log_file = Path(self.temp_dir) / "test.log"

    def teardown_method(self):
        """Clean up after tests."""
        import shutil

        shutil.rmtree(self.temp_dir, ignore_errors=True)

    def test_read_logfile_nonexistent(self):
        """Test reading non-existent log file."""

        async def test():
            result = await _read_logfile(self.log_file)
            assert isinstance(result, dict)
            assert "logs" in result

        asyncio.run(test())

    def test_read_logfile_with_content(self):
        """Test reading log file with content."""
        self.log_file.write_text("line 1\nline 2\nline 3\n")

        async def test():
            result = await _read_logfile(self.log_file, limit=2)
            assert isinstance(result, dict)
            assert "logs" in result

        asyncio.run(test())


class TestTailLog:
    """Test the tail_log async function."""

    def setup_method(self):
        """Set up test log file."""
        self.temp_dir = tempfile.mkdtemp()
        self.log_file = Path(self.temp_dir) / "test.log"

    def teardown_method(self):
        """Clean up after tests."""
        import shutil

        shutil.rmtree(self.temp_dir, ignore_errors=True)

    def test_tail_log_nonexistent(self):
        """Test tailing non-existent log file."""
        emitted_lines = []

        def emitter(line):
            emitted_lines.append(line)
            return False  # Stop after first emission

        async def test():
            try:
                await _tail_log(self.log_file, emitter, sleep_time=0.1)
            except Exception:
                pass  # Expected for non-existent file

        asyncio.run(test())


class TestLoadCookies:
    """Test the load_cookies function."""

    def setup_method(self):
        """Set up test cookie file."""
        self.temp_dir = tempfile.mkdtemp()
        self.cookie_file = Path(self.temp_dir) / "cookies.txt"

    def teardown_method(self):
        """Clean up after tests."""
        import shutil

        shutil.rmtree(self.temp_dir, ignore_errors=True)

    def test_load_cookies_invalid_file(self):
        """Test loading invalid cookie file."""
        self.cookie_file.write_text("invalid cookie content")

        try:
            valid, jar = load_cookies(str(self.cookie_file))
            assert isinstance(valid, bool)
            assert jar is not None or not valid
        except ValueError:
            # Expected for invalid cookie files
            assert True


class TestStrToDt:
    """Test the str_to_dt function."""

    def test_str_to_dt_basic(self):
        """Test basic string to datetime conversion."""
        try:
            result = str_to_dt("2023-01-02 12:00:00 UTC")
            assert isinstance(result, datetime)
        except ModuleNotFoundError:
            # Expected when dateparser is not available
            assert True

    def test_str_to_dt_relative(self):
        """Test relative time string."""
        try:
            result = str_to_dt("1 hour ago")
            assert isinstance(result, datetime)
        except (ModuleNotFoundError, ValueError):
            assert True


class TestInitClass:
    """Test the init_class function."""

    def test_init_class_basic(self):
        """Test basic class initialization."""

        @dataclass
        class TestClass:
            name: str = ""
            value: int = 0
            unused: str = "default"

        data = {"name": "test", "value": 42, "extra": "ignored"}

        result = init_class(TestClass, data)
        assert isinstance(result, TestClass)
        assert result.name == "test"
        assert result.value == 42
        assert result.unused == "default"  # Should use default


class TestLoadModules:
    """Test the load_modules function."""

    def setup_method(self):
        """Set up test module structure."""
        self.temp_dir = tempfile.mkdtemp()
        self.root_path = Path(self.temp_dir)
        self.module_dir = self.root_path / "test_modules"
        self.module_dir.mkdir()

        # Create test module files
        (self.module_dir / "__init__.py").write_text("")
        (self.module_dir / "test_module.py").write_text("# Test module")

    def teardown_method(self):
        """Clean up after tests."""
        import shutil

        shutil.rmtree(self.temp_dir, ignore_errors=True)

    def test_load_modules_basic(self):
        """Test basic module loading."""
        try:
            load_modules(self.root_path, self.module_dir)
            # Should not raise an exception
            assert True
        except Exception:
            # Module loading might fail in test environment
            assert True


class TestGetChannelImages:
    """Test the get_channel_images function."""

    def test_get_channel_images_poster_from_portrait(self):
        """Test extracting poster image from portrait ratio thumbnail."""
        from app.library.Utils import get_channel_images

        thumbnails = [{"url": "http://example.com/poster.jpg", "width": 200, "height": 300, "id": "some_id"}]

        result = get_channel_images(thumbnails)

        assert "poster" in result
        assert result["poster"] == "http://example.com/poster.jpg"

    def test_get_channel_images_thumb_from_square(self):
        """Test extracting thumbnail from square ratio."""
        from app.library.Utils import get_channel_images

        thumbnails = [{"url": "http://example.com/thumb.jpg", "width": 300, "height": 300, "id": "id"}]

        result = get_channel_images(thumbnails)

        assert "thumb" in result
        assert result["thumb"] == "http://example.com/thumb.jpg"

    def test_get_channel_images_banner_from_wide(self):
        """Test extracting banner from very wide image."""
        from app.library.Utils import get_channel_images

        thumbnails = [{"url": "http://example.com/banner.jpg", "width": 1920, "height": 200, "id": "id"}]

        result = get_channel_images(thumbnails)

        assert "banner" in result
        assert result["banner"] == "http://example.com/banner.jpg"

    def test_get_channel_images_icon_from_avatar(self):
        """Test extracting icon from avatar uncropped."""
        from app.library.Utils import get_channel_images

        thumbnails = [{"url": "http://example.com/icon.jpg", "id": "avatar_uncropped"}]

        result = get_channel_images(thumbnails)

        assert "icon" in result
        assert result["icon"] == "http://example.com/icon.jpg"

    def test_get_channel_images_landscape_from_banner_uncropped(self):
        """Test extracting landscape from banner uncropped."""
        from app.library.Utils import get_channel_images

        thumbnails = [{"url": "http://example.com/landscape.jpg", "id": "banner_uncropped"}]

        result = get_channel_images(thumbnails)

        assert "landscape" in result
        assert result["landscape"] == "http://example.com/landscape.jpg"

    def test_get_channel_images_empty_list(self):
        """Test with empty thumbnail list."""
        from app.library.Utils import get_channel_images

        result = get_channel_images([])

        assert result == {}

    def test_get_channel_images_fallbacks(self):
        """Test fallback logic for missing images."""
        from app.library.Utils import get_channel_images

        # Create image with fanart but no banner
        thumbnails = [{"url": "http://example.com/fanart.jpg", "width": 1920, "height": 200, "id": "id"}]

        result = get_channel_images(thumbnails)

        assert "fanart" in result
        assert "banner" in result  # Should fallback to fanart
        assert result["banner"] == result["fanart"]

    def test_get_channel_images_no_url(self):
        """Test handling of thumbnail without URL."""
        from app.library.Utils import get_channel_images

        thumbnails = [
            {"width": 300, "height": 300, "id": "id"},  # Missing URL
            {"url": "http://example.com/thumb.jpg", "width": 300, "height": 300, "id": "id"},
        ]

        result = get_channel_images(thumbnails)

        assert len(result) > 0
        assert result.get("thumb") == "http://example.com/thumb.jpg"


class TestIsSafeKey:
    """Test the _is_safe_key function (indirectly through merge_dict)."""

    def test_safe_key_normal_string(self):
        """Test that normal strings are considered safe keys."""
        from app.library.Utils import merge_dict

        source = {"normal_key": "value"}
        dest = {}

        result = merge_dict(source, dest)

        assert "normal_key" in result
        assert result["normal_key"] == "value"

    def test_unsafe_key_dunder_attributes(self):
        """Test that dunder attributes are blocked."""
        from app.library.Utils import merge_dict

        source = {"__class__": "should_not_merge", "normal_key": "value"}
        dest = {}

        result = merge_dict(source, dest)

        assert "__class__" not in result
        assert "normal_key" in result

    def test_unsafe_key_empty_string(self):
        """Test that empty keys are blocked."""
        from app.library.Utils import merge_dict

        source = {"": "empty_key_value", "normal_key": "value"}
        dest = {}

        result = merge_dict(source, dest)

        assert "" not in result
        assert "normal_key" in result

    def test_unsafe_key_whitespace_only(self):
        """Test that whitespace-only keys are blocked."""
        from app.library.Utils import merge_dict

        source = {"   ": "whitespace_key", "normal_key": "value"}
        dest = {}

        result = merge_dict(source, dest)

        assert "   " not in result
        assert "normal_key" in result

    def test_unsafe_key_non_string(self):
        """Test that non-string keys are blocked."""
        from app.library.Utils import merge_dict

        source = {123: "numeric_key", "normal_key": "value"}
        dest = {}

        result = merge_dict(source, dest)

        assert 123 not in result
        assert "normal_key" in result


class TestDecryptDataCornerCases:
    """Additional tests for decrypt_data edge cases."""

    def test_decrypt_data_invalid_base64(self):
        """Test decrypting invalid base64 data."""
        from app.library.Utils import decrypt_data

        result = decrypt_data("not_valid_base64!!!", b"k" * 32)

        assert result is None

    def test_decrypt_data_wrong_key(self):
        """Test decrypting with wrong key returns None."""
        from app.library.Utils import decrypt_data, encrypt_data

        correct_key = b"a" * 32
        wrong_key = b"z" * 32

        encrypted = encrypt_data("test data", correct_key)
        result = decrypt_data(encrypted, wrong_key)

        assert result is None, "Should return None on decryption failure with wrong key"

    def test_decrypt_data_truncated(self):
        """Test decrypting truncated encrypted data."""
        from app.library.Utils import decrypt_data

        result = decrypt_data("YQ==", b"k" * 32)

        assert result is None


class TestArgConverterAdvanced:
    """Advanced tests for arg_converter function."""

    def test_arg_converter_with_removed_options(self):
        """Test arg_converter with removed options tracking."""
        try:
            removed = []
            result = arg_converter("--quiet --skip-download", level=True, removed_options=removed)

            assert isinstance(result, dict)
        except (ModuleNotFoundError, AttributeError, ImportError):
            assert True

    def test_arg_converter_dumps_enabled(self):
        """Test arg_converter with dumps flag enabled."""
        try:
            result = arg_converter("--format best", dumps=True)

            # With dumps=True, result should still be a dict with JSON-serializable content
            assert isinstance(result, (dict, list))
        except (ModuleNotFoundError, AttributeError, ImportError):
            assert True


class TestCreateCookiesFile:
    """Test the create_cookies_file function."""

    def setup_method(self):
        """Set up test environment."""
        self.temp_dir = tempfile.mkdtemp()
        self.test_path = Path(self.temp_dir)

    def teardown_method(self):
        """Clean up after tests."""
        import shutil

        shutil.rmtree(self.temp_dir, ignore_errors=True)

    @patch("app.library.Utils.load_cookies")
    def test_create_cookies_file_with_path(self, mock_load_cookies):
        """Test creating a cookies file with a specific path."""
        from app.library.Utils import create_cookies_file

        mock_load_cookies.return_value = (True, MagicMock())
        cookie_path = self.test_path / "cookies" / "test_cookies.txt"

        result = create_cookies_file("session=abc123", file=cookie_path)

        assert result == cookie_path
        assert cookie_path.exists()
        assert cookie_path.is_file()
        assert cookie_path.read_text() == "session=abc123"
        mock_load_cookies.assert_called_once_with(cookie_path)

    @patch("app.library.config.Config")
    @patch("app.library.Utils.load_cookies")
    def test_create_cookies_file_without_path(self, mock_load_cookies, mock_config):
        """Test creating a cookies file without a specific path (auto temp file)."""
        from app.library.Utils import create_cookies_file

        mock_config_inst = MagicMock()
        mock_config_inst.temp_path = self.temp_dir
        mock_config.get_instance.return_value = mock_config_inst

        mock_load_cookies.return_value = (True, MagicMock())

        result = create_cookies_file("session=def456")

        assert result.exists()
        assert result.is_file()
        assert result.read_text() == "session=def456"
        assert result.parent == Path(self.temp_dir)
        mock_load_cookies.assert_called_once()

    @patch("app.library.Utils.load_cookies")
    def test_create_cookies_file_invalid_cookies(self, mock_load_cookies):
        """Test creating a cookies file with invalid cookies raises error."""
        from app.library.Utils import create_cookies_file

        mock_load_cookies.side_effect = ValueError("Invalid cookies")
        cookie_path = self.test_path / "bad_cookies.txt"

        with pytest.raises(ValueError, match="Invalid cookies"):
            create_cookies_file("invalid_data", file=cookie_path)

    @patch("app.library.Utils.load_cookies")
    def test_create_cookies_file_creates_parent_directory(self, mock_load_cookies):
        """Test that create_cookies_file creates parent directories as needed."""
        from app.library.Utils import create_cookies_file

        mock_load_cookies.return_value = (True, MagicMock())
        # Use a deeply nested path that doesn't exist yet
        cookie_path = self.test_path / "a" / "b" / "c" / "cookies.txt"

        result = create_cookies_file("test_data", file=cookie_path)

        assert result == cookie_path
        assert cookie_path.exists()
        assert cookie_path.parent == Path(self.test_path / "a" / "b" / "c")

    @patch("app.library.Utils.load_cookies")
    def test_create_cookies_file_with_special_characters(self, mock_load_cookies):
        """Test create_cookies_file with special characters in cookie data."""
        from app.library.Utils import create_cookies_file

        mock_load_cookies.return_value = (True, MagicMock())
        cookie_data = "session=abc123; path=/; domain=.example.com; secure; httponly"
        cookie_path = self.test_path / "special_cookies.txt"

        result = create_cookies_file(cookie_data, file=cookie_path)

        assert result == cookie_path
        assert cookie_path.read_text() == cookie_data

    @patch("app.library.Utils.load_cookies")
    def test_create_cookies_file_overwrites_existing(self, mock_load_cookies):
        """Test that create_cookies_file overwrites existing files."""
        from app.library.Utils import create_cookies_file

        mock_load_cookies.return_value = (True, MagicMock())
        cookie_path = self.test_path / "cookies.txt"

        # Create initial file
        cookie_path.parent.mkdir(parents=True, exist_ok=True)
        cookie_path.write_text("old_data")

        # Overwrite with new data
        result = create_cookies_file("new_data", file=cookie_path)

        assert result == cookie_path
        assert cookie_path.read_text() == "new_data"


class TestRenameFile:
    """Test rename_file function."""

    def test_rename_single_file_no_sidecars(self, tmp_path: Path):
        """Test renaming a single file without sidecar files."""
        # Create test file
        test_file = tmp_path / "video.mp4"
        test_file.write_text("test content")

        # Rename file
        new_path, sidecars = rename_file(test_file, "renamed_video.mp4")

        assert new_path.exists(), "Renamed file should exist"
        assert "renamed_video.mp4" == new_path.name, "File should have new name"
        assert not test_file.exists(), "Original file should not exist"
        assert 0 == len(sidecars), "Should have no sidecar files"

    def test_rename_file_with_subtitle_sidecar(self, tmp_path: Path):
        """Test renaming a file with subtitle sidecar."""
        # Create test files
        test_file = tmp_path / "video.mp4"
        test_file.write_text("test video")

        subtitle_file = tmp_path / "video.en.srt"
        subtitle_file.write_text("test subtitle")

        # Rename file
        new_path, sidecars = rename_file(test_file, "renamed_video.mp4")

        assert new_path.exists(), "Renamed file should exist"
        assert "renamed_video.mp4" == new_path.name, "File should have new name"
        assert not test_file.exists(), "Original file should not exist after rename"

        assert 1 == len(sidecars), "Should have renamed 1 sidecar file"
        old_sidecar, new_sidecar = sidecars[0]
        assert new_sidecar.exists()
        assert "renamed_video.en.srt" == new_sidecar.name
        assert old_sidecar == subtitle_file
        assert not subtitle_file.exists()

    def test_rename_file_with_multiple_sidecars(self, tmp_path: Path):
        """Test renaming a file with multiple sidecar files."""
        # Create test files
        test_file = tmp_path / "video.mp4"
        test_file.write_text("test video")

        subtitle_en = tmp_path / "video.en.srt"
        subtitle_en.write_text("english subtitle")

        subtitle_fr = tmp_path / "video.fr.srt"
        subtitle_fr.write_text("french subtitle")

        info_file = tmp_path / "video.info.json"
        info_file.write_text('{"title": "test"}')

        # Rename file
        new_path, sidecars = rename_file(test_file, "renamed_video.mp4")

        assert new_path.exists(), "Renamed file should exist"
        assert "renamed_video.mp4" == new_path.name, "File should have new name"
        assert not test_file.exists(), "Original file should not exist after rename"

        assert 3 == len(sidecars), "Should have renamed 3 sidecar files"

        # Check all sidecars were renamed
        sidecar_names = {new_sidecar.name for old_sidecar, new_sidecar in sidecars}
        assert "renamed_video.en.srt" in sidecar_names
        assert "renamed_video.fr.srt" in sidecar_names
        assert "renamed_video.info.json" in sidecar_names

        assert not subtitle_en.exists(), "Old subtitle file should not exist after rename"
        assert not subtitle_fr.exists(), "Old subtitle file should not exist after rename"
        assert not info_file.exists(), "Old info file should not exist after rename"

    def test_rename_file_destination_exists(self, tmp_path: Path):
        """Test renaming a file when destination already exists."""
        # Create test files
        test_file = tmp_path / "video.mp4"
        test_file.write_text("test content")

        existing_file = tmp_path / "renamed_video.mp4"
        existing_file.write_text("existing content")

        # Should raise ValueError
        with pytest.raises(ValueError, match="already exists"):
            rename_file(test_file, "renamed_video.mp4")

        assert test_file.exists(), "Original file should still exist when rename fails"
        assert existing_file.exists(), "Existing file should still exist when rename fails"

    def test_rename_file_sidecar_destination_exists(self, tmp_path: Path):
        """Test renaming when sidecar destination already exists."""
        # Create test files
        test_file = tmp_path / "video.mp4"
        test_file.write_text("test video")

        subtitle_file = tmp_path / "video.en.srt"
        subtitle_file.write_text("test subtitle")

        # Create conflicting sidecar destination
        conflicting_sidecar = tmp_path / "renamed_video.en.srt"
        conflicting_sidecar.write_text("existing subtitle")

        # Should raise ValueError
        with pytest.raises(ValueError, match=r"Sidecar destination.*already exists"):
            rename_file(test_file, "renamed_video.mp4")

        # Original files should still exist
        assert test_file.exists()
        assert subtitle_file.exists()
        assert conflicting_sidecar.exists()

    def test_rename_preserves_sidecar_extensions(self, tmp_path: Path):
        """Test that rename preserves complex sidecar extensions."""
        # Create test files with complex extensions
        test_file = tmp_path / "video.mp4"
        test_file.write_text("test video")

        subtitle_file = tmp_path / "video.en-US.ass"
        subtitle_file.write_text("test subtitle")

        thumb_file = tmp_path / "video.thumb.jpg"
        thumb_file.write_text("test thumb")

        # Rename file
        new_path, sidecars = rename_file(test_file, "renamed.mp4")

        assert new_path.exists(), "Renamed file should exist"
        assert "renamed.mp4" == new_path.name, "File should have new name"

        assert 2 == len(sidecars), "Should have renamed 2 sidecar files"
        sidecar_names = {new_sidecar.name for old_sidecar, new_sidecar in sidecars}
        assert "renamed.en-US.ass" in sidecar_names
        assert "renamed.thumb.jpg" in sidecar_names


class TestMoveFile:
    """Test move_file function."""

    def test_move_single_file_no_sidecars(self, tmp_path: Path):
        """Test moving a single file without sidecar files."""
        # Create test file
        source_dir = tmp_path / "source"
        source_dir.mkdir()
        test_file = source_dir / "video.mp4"
        test_file.write_text("test content")

        target_dir = tmp_path / "target"
        target_dir.mkdir()

        # Move file
        new_path, sidecars = move_file(test_file, target_dir)

        assert new_path.exists(), "Moved file should exist at destination"
        assert "video.mp4" == new_path.name, "File should keep same name"
        assert new_path.parent == target_dir, "File should be in target directory"
        assert not test_file.exists(), "Original file should not exist after move"
        assert 0 == len(sidecars), "Should have no sidecar files"

    def test_move_file_with_subtitle_sidecar(self, tmp_path: Path):
        """Test moving a file with subtitle sidecar."""
        # Create test files
        source_dir = tmp_path / "source"
        source_dir.mkdir()
        test_file = source_dir / "video.mp4"
        test_file.write_text("test video")

        subtitle_file = source_dir / "video.en.srt"
        subtitle_file.write_text("test subtitle")

        target_dir = tmp_path / "target"
        target_dir.mkdir()

        # Move file
        new_path, sidecars = move_file(test_file, target_dir)

        assert new_path.exists(), "Moved file should exist at destination"
        assert "video.mp4" == new_path.name, "File should keep same name"
        assert new_path.parent == target_dir, "File should be in target directory"
        assert not test_file.exists(), "Original file should not exist after move"

        assert 1 == len(sidecars), "Should have moved 1 sidecar file"
        old_sidecar, new_sidecar = sidecars[0]
        assert new_sidecar.exists()
        assert "video.en.srt" == new_sidecar.name
        assert new_sidecar.parent == target_dir
        assert old_sidecar == subtitle_file
        assert not subtitle_file.exists()

    def test_move_file_with_multiple_sidecars(self, tmp_path: Path):
        """Test moving a file with multiple sidecar files."""
        # Create test files
        source_dir = tmp_path / "source"
        source_dir.mkdir()
        test_file = source_dir / "video.mp4"
        test_file.write_text("test video")

        subtitle_en = source_dir / "video.en.srt"
        subtitle_en.write_text("english subtitle")

        subtitle_fr = source_dir / "video.fr.srt"
        subtitle_fr.write_text("french subtitle")

        info_file = source_dir / "video.info.json"
        info_file.write_text('{"title": "test"}')

        target_dir = tmp_path / "target"
        target_dir.mkdir()

        # Move file
        new_path, sidecars = move_file(test_file, target_dir)

        assert new_path.exists(), "Moved file should exist at destination"
        assert "video.mp4" == new_path.name, "File should keep same name"
        assert new_path.parent == target_dir, "File should be in target directory"
        assert not test_file.exists(), "Original file should not exist after move"

        assert 3 == len(sidecars), "Should have moved 3 sidecar files"

        # Check all sidecars were moved
        sidecar_names = {new_sidecar.name for old_sidecar, new_sidecar in sidecars}
        assert "video.en.srt" in sidecar_names
        assert "video.fr.srt" in sidecar_names
        assert "video.info.json" in sidecar_names

        # Check all are in target directory
        for _old_sidecar, new_sidecar in sidecars:
            assert new_sidecar.parent == target_dir, "All sidecars should be in target directory"

        assert not subtitle_en.exists(), "Old subtitle file should not exist after move"
        assert not subtitle_fr.exists(), "Old subtitle file should not exist after move"
        assert not info_file.exists(), "Old info file should not exist after move"

    def test_move_file_destination_exists(self, tmp_path: Path):
        """Test moving a file when destination already exists."""
        # Create test files
        source_dir = tmp_path / "source"
        source_dir.mkdir()
        test_file = source_dir / "video.mp4"
        test_file.write_text("test content")

        target_dir = tmp_path / "target"
        target_dir.mkdir()
        existing_file = target_dir / "video.mp4"
        existing_file.write_text("existing content")

        # Should raise ValueError
        with pytest.raises(ValueError, match="already exists"):
            move_file(test_file, target_dir)

        assert test_file.exists(), "Original file should still exist when move fails"
        assert existing_file.exists(), "Existing file should still exist when move fails"

    def test_move_file_sidecar_destination_exists(self, tmp_path: Path):
        """Test moving when sidecar destination already exists."""
        # Create test files
        source_dir = tmp_path / "source"
        source_dir.mkdir()
        test_file = source_dir / "video.mp4"
        test_file.write_text("test video")

        subtitle_file = source_dir / "video.en.srt"
        subtitle_file.write_text("test subtitle")

        target_dir = tmp_path / "target"
        target_dir.mkdir()

        # Create conflicting sidecar destination
        conflicting_sidecar = target_dir / "video.en.srt"
        conflicting_sidecar.write_text("existing subtitle")

        # Should raise ValueError
        with pytest.raises(ValueError, match=r"Sidecar destination.*already exists"):
            move_file(test_file, target_dir)

        # Original files should still exist
        assert test_file.exists()
        assert subtitle_file.exists()
        assert conflicting_sidecar.exists()

    def test_move_file_target_not_directory(self, tmp_path: Path):
        """Test moving when target is not a directory."""
        # Create test file
        source_dir = tmp_path / "source"
        source_dir.mkdir()
        test_file = source_dir / "video.mp4"
        test_file.write_text("test content")

        # Create a file (not directory) as target
        target_file = tmp_path / "target.txt"
        target_file.write_text("not a directory")

        # Should raise ValueError
        with pytest.raises(ValueError, match="not a directory"):
            move_file(test_file, target_file)

        # Original file should still exist
        assert test_file.exists()

    def test_move_file_target_does_not_exist(self, tmp_path: Path):
        """Test moving when target directory doesn't exist."""
        # Create test file
        source_dir = tmp_path / "source"
        source_dir.mkdir()
        test_file = source_dir / "video.mp4"
        test_file.write_text("test content")

        target_dir = tmp_path / "nonexistent"

        # Should raise ValueError
        with pytest.raises(ValueError, match="does not exist"):
            move_file(test_file, target_dir)

        # Original file should still exist
        assert test_file.exists()
