"""
Tests for GateWay File Transfer Module.

Tests file detection, serialization, transfer, and restoration.
Includes tests for directory/folder support.
"""

from pathlib import Path

import pytest

from verlex_server.gateway.core.file_transfer import (
    INLINE_FILE_THRESHOLD,
    DirectoryReference,
    FileReference,
    FileResult,
    FileTransferError,
    FileTransferMode,
    compute_file_checksum,
    create_directory_reference,
    create_file_reference,
    detect_directory_modifications,
    detect_file_modifications,
    detect_files_in_args,
    is_directory_path,
    is_file_path,
    process_args_for_files,
    process_result_for_files,
    reconstruct_files_in_args,
    restore_files_from_result,
    zip_directory,
)


class TestFileDetection:
    """Tests for file path detection."""

    def test_detect_path_object(self, tmp_path):
        """Test detection of pathlib.Path objects."""
        # Create a real file
        test_file = tmp_path / "test.txt"
        test_file.write_text("hello world")

        assert is_file_path(test_file) is True
        assert is_file_path(tmp_path) is False  # Directory, not file

    def test_detect_string_path(self, tmp_path):
        """Test detection of string paths."""
        test_file = tmp_path / "test.txt"
        test_file.write_text("hello world")

        assert is_file_path(str(test_file)) is True
        assert is_file_path(str(tmp_path)) is False  # Directory

    def test_non_existent_path(self, tmp_path):
        """Non-existent paths should not be detected as files."""
        fake_path = tmp_path / "does_not_exist.txt"
        assert is_file_path(fake_path) is False
        assert is_file_path(str(fake_path)) is False

    def test_urls_not_detected(self):
        """URLs should not be detected as file paths."""
        assert is_file_path("http://example.com/file.txt") is False
        assert is_file_path("https://example.com/file.txt") is False
        assert is_file_path("s3://bucket/key") is False
        assert is_file_path("gs://bucket/key") is False

    def test_regular_strings_not_detected(self):
        """Regular strings should not be detected as file paths."""
        assert is_file_path("hello world") is False
        assert is_file_path("") is False
        assert is_file_path("some random text") is False

    def test_non_string_types(self):
        """Non-string/Path types should not be detected."""
        assert is_file_path(123) is False
        assert is_file_path(None) is False
        assert is_file_path(["/path/to/file"]) is False
        assert is_file_path({"path": "/file"}) is False

    def test_directory_detection(self, tmp_path):
        """Test directory path detection."""
        assert is_directory_path(tmp_path) is True
        assert is_directory_path(str(tmp_path)) is True

        # File should not be detected as directory
        test_file = tmp_path / "test.txt"
        test_file.write_text("content")
        assert is_directory_path(test_file) is False


class TestFileDetectionInArgs:
    """Tests for detecting files in function arguments."""

    def test_detect_in_positional_args(self, tmp_path):
        """Test detection in positional arguments."""
        file1 = tmp_path / "file1.txt"
        file1.write_text("content 1")

        files, size = detect_files_in_args((file1, "hello", 123), {})

        assert len(files) == 1
        assert files[0][0] == "args[0]"
        assert size > 0

    def test_detect_in_kwargs(self, tmp_path):
        """Test detection in keyword arguments."""
        file1 = tmp_path / "input.txt"
        file1.write_text("content")

        files, size = detect_files_in_args((), {"input_file": file1})

        assert len(files) == 1
        assert files[0][0] == "kwargs['input_file']"

    def test_detect_in_nested_list(self, tmp_path):
        """Test detection in nested lists."""
        file1 = tmp_path / "file1.txt"
        file1.write_text("content 1")
        file2 = tmp_path / "file2.txt"
        file2.write_text("content 2")

        files, size = detect_files_in_args(([file1, file2],), {})

        assert len(files) == 2
        assert files[0][0] == "args[0][0]"
        assert files[1][0] == "args[0][1]"

    def test_detect_in_nested_dict(self, tmp_path):
        """Test detection in nested dictionaries."""
        file1 = tmp_path / "file1.txt"
        file1.write_text("content")

        files, size = detect_files_in_args(
            (),
            {"config": {"input": file1, "value": 42}}
        )

        assert len(files) == 1
        assert "input" in files[0][0]

    def test_no_files(self):
        """Test with arguments containing no files."""
        files, size = detect_files_in_args(
            (1, 2, "hello"),
            {"key": "value", "num": 123}
        )

        assert len(files) == 0
        assert size == 0


class TestFileReference:
    """Tests for FileReference creation and serialization."""

    def test_create_small_file_reference(self, tmp_path):
        """Small files should use inline transfer mode."""
        test_file = tmp_path / "small.txt"
        test_file.write_text("small content")

        ref = create_file_reference(test_file)

        assert ref.filename == "small.txt"
        assert ref.transfer_mode == FileTransferMode.INLINE
        assert ref.inline_content is not None
        assert ref.size_bytes == len("small content")
        assert ref.checksum is not None

    def test_create_large_file_reference(self, tmp_path):
        """Large files should use object storage transfer mode."""
        test_file = tmp_path / "large.bin"
        # Create a file larger than INLINE_FILE_THRESHOLD
        large_content = b"x" * (INLINE_FILE_THRESHOLD + 1000)
        test_file.write_bytes(large_content)

        ref = create_file_reference(test_file, job_id="test_job")

        assert ref.transfer_mode == FileTransferMode.OBJECT_STORAGE
        assert ref.inline_content is None
        assert ref.storage_key is not None
        assert "test_job" in ref.storage_key

    def test_file_reference_to_dict(self, tmp_path):
        """Test FileReference serialization to dict."""
        test_file = tmp_path / "test.txt"
        test_file.write_text("content")

        ref = create_file_reference(test_file)
        d = ref.to_dict()

        assert d["_type"] == "FileReference"
        assert d["filename"] == "test.txt"
        assert "original_path" in d
        assert "checksum" in d

    def test_file_reference_from_dict(self, tmp_path):
        """Test FileReference deserialization from dict."""
        test_file = tmp_path / "test.txt"
        test_file.write_text("content")

        ref = create_file_reference(test_file)
        d = ref.to_dict()

        restored = FileReference.from_dict(d)

        assert restored.filename == ref.filename
        assert restored.checksum == ref.checksum
        assert restored.size_bytes == ref.size_bytes

    def test_get_content(self, tmp_path):
        """Test getting content from inline file reference."""
        test_file = tmp_path / "test.txt"
        content = "test content here"
        test_file.write_text(content)

        ref = create_file_reference(test_file)
        retrieved = ref.get_content()

        assert retrieved == content.encode("utf-8")

    def test_file_not_found(self, tmp_path):
        """Test error when file doesn't exist."""
        fake_file = tmp_path / "nonexistent.txt"

        with pytest.raises(FileTransferError, match="File not found"):
            create_file_reference(fake_file)

    def test_directory_error(self, tmp_path):
        """Test error when path is a directory."""
        with pytest.raises(FileTransferError, match="Not a file"):
            create_file_reference(tmp_path)


class TestProcessArgs:
    """Tests for processing function arguments."""

    def test_process_single_file(self, tmp_path):
        """Test processing a single file argument."""
        test_file = tmp_path / "input.txt"
        test_file.write_text("input data")

        processed_args, processed_kwargs, refs = process_args_for_files(
            (test_file,), {}
        )

        assert len(refs) == 1
        assert isinstance(processed_args[0], FileReference)
        assert processed_args[0].filename == "input.txt"

    def test_process_mixed_args(self, tmp_path):
        """Test processing mixed file and non-file arguments."""
        test_file = tmp_path / "data.txt"
        test_file.write_text("data")

        processed_args, processed_kwargs, refs = process_args_for_files(
            (42, test_file, "hello"),
            {"count": 10}
        )

        assert len(refs) == 1
        assert processed_args[0] == 42
        assert isinstance(processed_args[1], FileReference)
        assert processed_args[2] == "hello"
        assert processed_kwargs["count"] == 10

    def test_process_multiple_files(self, tmp_path):
        """Test processing multiple file arguments."""
        file1 = tmp_path / "file1.txt"
        file1.write_text("content 1")
        file2 = tmp_path / "file2.txt"
        file2.write_text("content 2")

        processed_args, processed_kwargs, refs = process_args_for_files(
            (file1, file2), {}
        )

        assert len(refs) == 2
        assert isinstance(processed_args[0], FileReference)
        assert isinstance(processed_args[1], FileReference)


class TestReconstructFiles:
    """Tests for reconstructing files on the remote side."""

    def test_reconstruct_single_file(self, tmp_path):
        """Test reconstructing a single file."""
        # Create original file
        original = tmp_path / "original.txt"
        original.write_text("original content")

        # Create reference
        ref = create_file_reference(original)

        # Simulate serialization
        args = (ref.to_dict(),)

        # Create temp dir for reconstruction
        reconstruct_dir = tmp_path / "reconstruct"
        reconstruct_dir.mkdir()

        # Reconstruct
        recon_args, recon_kwargs, mapping = reconstruct_files_in_args(
            args, {}, temp_dir=str(reconstruct_dir)
        )

        # Verify
        assert len(mapping) == 1
        reconstructed_path = recon_args[0]
        assert reconstructed_path.exists()
        assert reconstructed_path.read_text() == "original content"

    def test_reconstruct_preserves_non_files(self, tmp_path):
        """Test that non-file arguments are preserved."""
        original = tmp_path / "file.txt"
        original.write_text("content")
        ref = create_file_reference(original)

        args = (42, ref.to_dict(), "hello")
        kwargs = {"key": "value"}

        recon_args, recon_kwargs, mapping = reconstruct_files_in_args(
            args, kwargs
        )

        assert recon_args[0] == 42
        assert isinstance(recon_args[1], Path)
        assert recon_args[2] == "hello"
        assert recon_kwargs["key"] == "value"

    def test_checksum_verification(self, tmp_path):
        """Test that checksum mismatch raises error."""
        original = tmp_path / "file.txt"
        original.write_text("content")

        ref = create_file_reference(original)
        ref_dict = ref.to_dict()

        # Corrupt the checksum
        ref_dict["checksum"] = "invalid_checksum"

        with pytest.raises(FileTransferError, match="Checksum mismatch"):
            reconstruct_files_in_args((ref_dict,), {})


class TestFileModificationDetection:
    """Tests for detecting file modifications."""

    def test_detect_modified_file(self, tmp_path):
        """Test detection of modified files."""
        # Original file
        original = tmp_path / "original.txt"
        original.write_text("original")
        original_checksum = compute_file_checksum(original.read_bytes())

        # Modified file
        modified = tmp_path / "modified.txt"
        modified.write_text("modified content")

        path_mapping = {str(original): modified}
        checksums = {str(original): original_checksum}

        modified_files = detect_file_modifications(path_mapping, checksums)

        assert len(modified_files) == 1
        assert modified_files[0].was_modified is True
        assert modified_files[0].content == b"modified content"

    def test_unmodified_file_not_returned(self, tmp_path):
        """Test that unmodified files are not in results."""
        original = tmp_path / "file.txt"
        original.write_text("content")
        checksum = compute_file_checksum(original.read_bytes())

        # Same content, same file
        path_mapping = {str(original): original}
        checksums = {str(original): checksum}

        modified_files = detect_file_modifications(path_mapping, checksums)

        assert len(modified_files) == 0


class TestFileResult:
    """Tests for FileResult serialization and restoration."""

    def test_file_result_to_dict(self, tmp_path):
        """Test FileResult serialization."""
        test_file = tmp_path / "result.txt"
        test_file.write_text("result data")

        from verlex_server.gateway.core.file_transfer import create_file_result_from_path
        result = create_file_result_from_path(test_file)

        d = result.to_dict()

        assert d["_type"] == "FileResult"
        assert d["filename"] == "result.txt"
        assert "content" in d  # Base64 encoded

    def test_file_result_from_dict(self, tmp_path):
        """Test FileResult deserialization."""
        test_file = tmp_path / "result.txt"
        test_file.write_text("result data")

        from verlex_server.gateway.core.file_transfer import create_file_result_from_path
        result = create_file_result_from_path(test_file)
        d = result.to_dict()

        restored = FileResult.from_dict(d)

        assert restored.filename == result.filename
        assert restored.content == result.content
        assert restored.checksum == result.checksum

    def test_save_to_original_path(self, tmp_path):
        """Test saving FileResult to original path."""
        # Create result
        content = b"file content here"
        result = FileResult(
            path=str(tmp_path / "output.txt"),
            filename="output.txt",
            content=content,
            size_bytes=len(content),
            checksum=compute_file_checksum(content),
        )

        saved_path = result.save_to()

        assert saved_path.exists()
        assert saved_path.read_bytes() == content

    def test_save_to_custom_path(self, tmp_path):
        """Test saving FileResult to custom path."""
        content = b"content"
        result = FileResult(
            path="/some/original/path.txt",
            filename="path.txt",
            content=content,
            size_bytes=len(content),
            checksum=compute_file_checksum(content),
        )

        custom_path = tmp_path / "custom_output.txt"
        saved_path = result.save_to(custom_path)

        assert saved_path == custom_path
        assert saved_path.read_bytes() == content


class TestRestoreFilesFromResult:
    """Tests for restoring files from function results."""

    def test_restore_single_file(self, tmp_path):
        """Test restoring a single file from result."""
        content = b"restored content"
        result_dict = {
            "_type": "FileResult",
            "path": str(tmp_path / "restored.txt"),
            "filename": "restored.txt",
            "content": __import__("base64").b64encode(content).decode("utf-8"),
            "size_bytes": len(content),
            "checksum": compute_file_checksum(content),
        }

        restored, saved_paths = restore_files_from_result(result_dict)

        assert len(saved_paths) == 1
        assert saved_paths[0].exists()
        assert saved_paths[0].read_bytes() == content

    def test_restore_nested_in_dict(self, tmp_path):
        """Test restoring files nested in result dictionaries."""
        content = b"nested content"
        result = {
            "status": "success",
            "output_file": {
                "_type": "FileResult",
                "path": str(tmp_path / "nested.txt"),
                "filename": "nested.txt",
                "content": __import__("base64").b64encode(content).decode("utf-8"),
                "size_bytes": len(content),
                "checksum": compute_file_checksum(content),
            }
        }

        restored, saved_paths = restore_files_from_result(result)

        assert len(saved_paths) == 1
        assert restored["status"] == "success"
        assert isinstance(restored["output_file"], Path)

    def test_restore_to_custom_directory(self, tmp_path):
        """Test restoring files to a custom directory."""
        content = b"content"
        result_dict = {
            "_type": "FileResult",
            "path": "/original/path/file.txt",
            "filename": "file.txt",
            "content": __import__("base64").b64encode(content).decode("utf-8"),
            "size_bytes": len(content),
            "checksum": compute_file_checksum(content),
        }

        output_dir = tmp_path / "custom_output"
        output_dir.mkdir()

        restored, saved_paths = restore_files_from_result(
            result_dict,
            output_dir=output_dir
        )

        assert saved_paths[0].parent == output_dir
        assert saved_paths[0].name == "file.txt"


class TestProcessResultForFiles:
    """Tests for processing function results with files."""

    def test_process_path_result(self, tmp_path):
        """Test processing a Path result."""
        # Create a file
        result_file = tmp_path / "result.txt"
        result_file.write_text("result content")

        processed, file_results = process_result_for_files(result_file)

        assert len(file_results) == 1
        assert file_results[0].filename == "result.txt"
        assert file_results[0].was_created is True

    def test_process_dict_with_path(self, tmp_path):
        """Test processing a dict containing paths."""
        result_file = tmp_path / "output.txt"
        result_file.write_text("output")

        result = {
            "status": "success",
            "output": result_file,
            "count": 42,
        }

        processed, file_results = process_result_for_files(result)

        assert len(file_results) == 1
        assert processed["status"] == "success"
        assert processed["count"] == 42


class TestIntegration:
    """Integration tests for the complete file transfer flow."""

    def test_full_roundtrip(self, tmp_path):
        """Test complete file transfer: client -> remote -> client."""
        # 1. Create input file (client side)
        input_file = tmp_path / "input.txt"
        input_file.write_text("Hello, Gateway!")

        # 2. Process arguments for transfer
        args = (input_file, 42)
        kwargs = {"name": "test"}

        processed_args, processed_kwargs, refs = process_args_for_files(
            args, kwargs, job_id="test_job"
        )

        # Verify file was replaced with reference
        assert isinstance(processed_args[0], FileReference)
        assert processed_args[1] == 42

        # 3. Serialize to dict (simulate network transfer)
        serialized_args = (processed_args[0].to_dict(), processed_args[1])

        # 4. Reconstruct on remote side
        remote_dir = tmp_path / "remote"
        remote_dir.mkdir()

        recon_args, recon_kwargs, mapping = reconstruct_files_in_args(
            serialized_args, kwargs, temp_dir=str(remote_dir)
        )

        # Verify file was reconstructed
        remote_file = recon_args[0]
        assert remote_file.exists()
        assert remote_file.read_text() == "Hello, Gateway!"

        # 5. Simulate function modifying the file
        remote_file.write_text("Modified by Gateway!")

        # 6. Detect modifications and prepare result
        original_checksums = {r.original_path: r.checksum for r in refs}
        modified_files = detect_file_modifications(mapping, original_checksums)

        assert len(modified_files) == 1
        assert modified_files[0].was_modified is True

        # 7. Restore on client side
        client_output = tmp_path / "client_output"
        client_output.mkdir()

        result_dict = modified_files[0].to_dict()
        restored, saved_paths = restore_files_from_result(
            result_dict,
            output_dir=client_output
        )

        # Verify restoration
        assert len(saved_paths) == 1
        assert saved_paths[0].read_text() == "Modified by Gateway!"


class TestEdgeCases:
    """Tests for edge cases and error handling."""

    def test_empty_args(self):
        """Test with empty arguments."""
        processed_args, processed_kwargs, refs = process_args_for_files((), {})

        assert processed_args == ()
        assert processed_kwargs == {}
        assert len(refs) == 0

    def test_binary_file(self, tmp_path):
        """Test with binary file content."""
        binary_file = tmp_path / "binary.bin"
        binary_content = bytes(range(256))
        binary_file.write_bytes(binary_content)

        ref = create_file_reference(binary_file)
        retrieved = ref.get_content()

        assert retrieved == binary_content

    def test_unicode_filename(self, tmp_path):
        """Test with unicode characters in filename."""
        unicode_file = tmp_path / "données_文件.txt"
        unicode_file.write_text("unicode content")

        ref = create_file_reference(unicode_file)

        assert "données_文件.txt" in ref.filename or "données_文件" in ref.filename

    def test_deep_nesting(self, tmp_path):
        """Test with deeply nested file paths."""
        test_file = tmp_path / "file.txt"
        test_file.write_text("content")

        deeply_nested = {
            "level1": {
                "level2": {
                    "level3": [
                        {"file": test_file}
                    ]
                }
            }
        }

        _, _, refs = process_args_for_files((), deeply_nested)

        assert len(refs) == 1

# ============================================================================
# Directory Support Tests
# ============================================================================

class TestDirectoryDetection:
    """Tests for directory path detection."""

    def test_detect_directory_path(self, tmp_path):
        """Test detection of pathlib.Path directories."""
        # Create a directory with files
        test_dir = tmp_path / "images"
        test_dir.mkdir()
        (test_dir / "img1.png").write_bytes(b"fake image 1")
        (test_dir / "img2.png").write_bytes(b"fake image 2")

        assert is_directory_path(test_dir) is True
        assert is_file_path(test_dir) is False

    def test_detect_directory_string_path(self, tmp_path):
        """Test detection of string directory paths."""
        test_dir = tmp_path / "data"
        test_dir.mkdir()
        (test_dir / "file.txt").write_text("content")

        assert is_directory_path(str(test_dir)) is True
        assert is_file_path(str(test_dir)) is False

    def test_detect_directory_in_args(self, tmp_path):
        """Test directory detection in function arguments."""
        test_dir = tmp_path / "images"
        test_dir.mkdir()
        (test_dir / "img1.png").write_bytes(b"image 1")
        (test_dir / "img2.png").write_bytes(b"image 2")

        paths_found, total_size = detect_files_in_args((test_dir,), {})

        assert len(paths_found) == 1
        assert paths_found[0][0] == "args[0]"
        assert paths_found[0][2] is True  # is_directory flag
        assert total_size > 0


class TestDirectoryReference:
    """Tests for DirectoryReference creation and serialization."""

    def test_create_directory_reference(self, tmp_path):
        """Test creating a DirectoryReference from a directory."""
        test_dir = tmp_path / "images"
        test_dir.mkdir()
        (test_dir / "img1.png").write_bytes(b"fake png content 1")
        (test_dir / "img2.jpg").write_bytes(b"fake jpg content 2")
        (test_dir / "img3.gif").write_bytes(b"fake gif content 3")

        ref = create_directory_reference(test_dir)

        assert ref.dirname == "images"
        assert ref.file_count == 3
        assert ref.size_bytes > 0
        assert ref.transfer_mode == FileTransferMode.INLINE
        assert ref.original_file_checksums is not None
        assert len(ref.original_file_checksums) == 3

    def test_directory_reference_to_dict(self, tmp_path):
        """Test DirectoryReference serialization to dict."""
        test_dir = tmp_path / "data"
        test_dir.mkdir()
        (test_dir / "file.txt").write_text("content")

        ref = create_directory_reference(test_dir)
        d = ref.to_dict()

        assert d["_type"] == "DirectoryReference"
        assert d["dirname"] == "data"
        assert d["file_count"] == 1
        assert "inline_content" in d

    def test_directory_reference_from_dict(self, tmp_path):
        """Test DirectoryReference deserialization from dict."""
        test_dir = tmp_path / "test"
        test_dir.mkdir()
        (test_dir / "file.txt").write_text("content")

        ref = create_directory_reference(test_dir)
        d = ref.to_dict()

        restored = DirectoryReference.from_dict(d)

        assert restored.dirname == ref.dirname
        assert restored.file_count == ref.file_count
        assert restored.checksum == ref.checksum

    def test_empty_directory_error(self, tmp_path):
        """Test that empty directories raise an error."""
        empty_dir = tmp_path / "empty"
        empty_dir.mkdir()

        with pytest.raises(FileTransferError, match="empty"):
            create_directory_reference(empty_dir)

    def test_nested_directory(self, tmp_path):
        """Test directory with nested subdirectories."""
        test_dir = tmp_path / "project"
        test_dir.mkdir()
        (test_dir / "src").mkdir()
        (test_dir / "src" / "main.py").write_text("print('hello')")
        (test_dir / "tests").mkdir()
        (test_dir / "tests" / "test_main.py").write_text("def test(): pass")

        ref = create_directory_reference(test_dir)

        assert ref.file_count == 2
        assert "src/main.py" in ref.original_file_checksums or "src\\main.py" in str(ref.original_file_checksums)


class TestProcessArgsWithDirectories:
    """Tests for processing function arguments with directories."""

    def test_process_directory_arg(self, tmp_path):
        """Test processing a directory argument."""
        test_dir = tmp_path / "images"
        test_dir.mkdir()
        (test_dir / "img1.png").write_bytes(b"image 1")
        (test_dir / "img2.png").write_bytes(b"image 2")

        processed_args, _, refs = process_args_for_files((test_dir,), {})

        assert len(refs) == 1
        assert isinstance(refs[0], DirectoryReference)
        assert refs[0].dirname == "images"
        assert refs[0].file_count == 2

    def test_process_mixed_files_and_directories(self, tmp_path):
        """Test processing mixed file and directory arguments."""
        # Create a file
        single_file = tmp_path / "single.txt"
        single_file.write_text("single file")

        # Create a directory
        test_dir = tmp_path / "images"
        test_dir.mkdir()
        (test_dir / "img1.png").write_bytes(b"image 1")

        processed_args, _, refs = process_args_for_files(
            (single_file, test_dir), {}
        )

        assert len(refs) == 2
        assert isinstance(refs[0], FileReference)
        assert isinstance(refs[1], DirectoryReference)


class TestReconstructDirectories:
    """Tests for reconstructing directories on the remote side."""

    def test_reconstruct_directory(self, tmp_path):
        """Test reconstructing a directory from DirectoryReference."""
        # Create original directory
        original_dir = tmp_path / "original"
        original_dir.mkdir()
        (original_dir / "file1.txt").write_text("content 1")
        (original_dir / "file2.txt").write_text("content 2")

        # Create reference
        ref = create_directory_reference(original_dir)

        # Serialize to dict
        args = (ref.to_dict(),)

        # Create temp dir for reconstruction
        reconstruct_dir = tmp_path / "reconstruct"
        reconstruct_dir.mkdir()

        # Reconstruct
        recon_args, _, mapping = reconstruct_files_in_args(
            args, {}, temp_dir=str(reconstruct_dir)
        )

        # Verify
        assert len(mapping) == 1
        reconstructed_path = recon_args[0]
        assert reconstructed_path.exists()
        assert reconstructed_path.is_dir()
        assert (reconstructed_path / "file1.txt").read_text() == "content 1"
        assert (reconstructed_path / "file2.txt").read_text() == "content 2"

    def test_reconstruct_mixed_files_and_directories(self, tmp_path):
        """Test reconstructing mixed files and directories."""
        # Create file
        single_file = tmp_path / "file.txt"
        single_file.write_text("file content")

        # Create directory
        test_dir = tmp_path / "dir"
        test_dir.mkdir()
        (test_dir / "inner.txt").write_text("inner content")

        # Create references
        file_ref = create_file_reference(single_file)
        dir_ref = create_directory_reference(test_dir)

        args = (file_ref.to_dict(), dir_ref.to_dict())

        # Reconstruct
        recon_args, _, mapping = reconstruct_files_in_args(args, {})

        assert len(mapping) == 2
        assert recon_args[0].is_file()
        assert recon_args[1].is_dir()


class TestDirectoryModificationDetection:
    """Tests for detecting directory modifications."""

    def test_detect_modified_directory(self, tmp_path):
        """Test detection of modified directories."""
        # Create directory
        test_dir = tmp_path / "data"
        test_dir.mkdir()
        (test_dir / "file.txt").write_text("original")

        # Get original checksums
        _, _, original_checksums = zip_directory(test_dir)

        # Modify a file
        (test_dir / "file.txt").write_text("modified content")

        path_mapping = {"/original/path": test_dir}
        dir_checksums = {"/original/path": original_checksums}

        modified_dirs = detect_directory_modifications(path_mapping, dir_checksums)

        assert len(modified_dirs) == 1
        assert modified_dirs[0].was_modified is True

    def test_detect_new_file_in_directory(self, tmp_path):
        """Test detection when a new file is added to directory."""
        test_dir = tmp_path / "data"
        test_dir.mkdir()
        (test_dir / "file1.txt").write_text("original")

        _, _, original_checksums = zip_directory(test_dir)

        # Add a new file
        (test_dir / "file2.txt").write_text("new file")

        path_mapping = {"/original": test_dir}
        dir_checksums = {"/original": original_checksums}

        modified_dirs = detect_directory_modifications(path_mapping, dir_checksums)

        assert len(modified_dirs) == 1

    def test_unmodified_directory_not_returned(self, tmp_path):
        """Test that unmodified directories are not in results."""
        test_dir = tmp_path / "data"
        test_dir.mkdir()
        (test_dir / "file.txt").write_text("content")

        _, _, original_checksums = zip_directory(test_dir)

        # Don't modify anything
        path_mapping = {"/original": test_dir}
        dir_checksums = {"/original": original_checksums}

        modified_dirs = detect_directory_modifications(path_mapping, dir_checksums)

        assert len(modified_dirs) == 0


class TestDirectoryResult:
    """Tests for DirectoryResult serialization and restoration."""

    def test_directory_result_to_dict(self, tmp_path):
        """Test DirectoryResult serialization."""
        test_dir = tmp_path / "result"
        test_dir.mkdir()
        (test_dir / "output.txt").write_text("output data")

        from verlex_server.gateway.core.file_transfer import create_directory_result_from_path
        result = create_directory_result_from_path(test_dir)

        d = result.to_dict()

        assert d["_type"] == "DirectoryResult"
        assert d["dirname"] == "result"
        assert d["file_count"] == 1

    def test_directory_result_save_to(self, tmp_path):
        """Test saving DirectoryResult to disk."""
        # Create source directory
        source_dir = tmp_path / "source"
        source_dir.mkdir()
        (source_dir / "file1.txt").write_text("content 1")
        (source_dir / "file2.txt").write_text("content 2")

        from verlex_server.gateway.core.file_transfer import create_directory_result_from_path
        result = create_directory_result_from_path(source_dir)

        # Save to new location
        save_path = tmp_path / "restored"
        saved = result.save_to(save_path)

        assert saved.exists()
        assert saved.is_dir()
        assert (saved / "file1.txt").read_text() == "content 1"
        assert (saved / "file2.txt").read_text() == "content 2"


class TestRestoreDirectoriesFromResult:
    """Tests for restoring directories from function results."""

    def test_restore_directory_result(self, tmp_path):
        """Test restoring a DirectoryResult."""
        # Create source directory
        source_dir = tmp_path / "source"
        source_dir.mkdir()
        (source_dir / "data.txt").write_text("some data")

        from verlex_server.gateway.core.file_transfer import create_directory_result_from_path
        result = create_directory_result_from_path(source_dir)
        result_dict = result.to_dict()

        # Restore
        output_dir = tmp_path / "output"
        output_dir.mkdir()

        restored, saved_paths = restore_files_from_result(
            result_dict,
            output_dir=output_dir
        )

        assert len(saved_paths) == 1
        assert saved_paths[0].is_dir()
        assert (saved_paths[0] / "data.txt").read_text() == "some data"


class TestDirectoryIntegration:
    """Integration tests for directory transfer flow."""

    def test_full_directory_roundtrip(self, tmp_path):
        """Test complete directory transfer: client -> remote -> client."""
        # 1. Create input directory (client side)
        input_dir = tmp_path / "input_images"
        input_dir.mkdir()
        (input_dir / "img1.png").write_bytes(b"image 1 content")
        (input_dir / "img2.png").write_bytes(b"image 2 content")
        (input_dir / "img3.png").write_bytes(b"image 3 content")

        # 2. Process arguments for transfer
        args = (input_dir,)
        kwargs = {"batch_size": 32}

        processed_args, processed_kwargs, refs = process_args_for_files(
            args, kwargs, job_id="test_job"
        )

        # Verify directory was replaced with reference
        assert isinstance(refs[0], DirectoryReference)
        assert refs[0].file_count == 3

        # 3. Serialize to dict (simulate network transfer)
        serialized_args = (refs[0].to_dict(),)

        # 4. Reconstruct on remote side
        remote_dir = tmp_path / "remote"
        remote_dir.mkdir()

        recon_args, recon_kwargs, mapping = reconstruct_files_in_args(
            serialized_args, kwargs, temp_dir=str(remote_dir)
        )

        # Verify directory was reconstructed
        remote_images = recon_args[0]
        assert remote_images.exists()
        assert remote_images.is_dir()
        assert len(list(remote_images.glob("*.png"))) == 3

        # 5. Simulate function modifying files
        (remote_images / "img1.png").write_bytes(b"PROCESSED image 1")
        (remote_images / "img4.png").write_bytes(b"NEW image 4")  # New file

        # 6. Detect modifications and prepare result
        original_dir_checksums = {
            refs[0].original_path: refs[0].original_file_checksums
        }
        modified_dirs = detect_directory_modifications(mapping, original_dir_checksums)

        assert len(modified_dirs) == 1
        assert modified_dirs[0].was_modified is True
        assert modified_dirs[0].file_count == 4  # 3 original + 1 new

        # 7. Restore on client side
        client_output = tmp_path / "client_output"
        client_output.mkdir()

        result_dict = modified_dirs[0].to_dict()
        restored, saved_paths = restore_files_from_result(
            result_dict,
            output_dir=client_output
        )

        # Verify restoration
        assert len(saved_paths) == 1
        restored_dir = saved_paths[0]
        assert restored_dir.is_dir()
        assert (restored_dir / "img1.png").read_bytes() == b"PROCESSED image 1"
        assert (restored_dir / "img4.png").read_bytes() == b"NEW image 4"
