"""Recipe "download-file": download the files of a document's latest version to a folder.

Usage: python recipes/download_file.py [document ID, default 437]
[folder, default a "mfiles-grpc-samples" folder in the temporary folder]
"""

import re
import sys
import tempfile
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from mfiles_client import run  # noqa: E402
from mfiles_values import obj_id  # noqa: E402
from recipe_helpers import connect, describe, int_arg, string_arg  # noqa: E402

from mfiles import IRPCObjectOperations_pb2 as ops  # noqa: E402
from mfiles import IRPCObjectOperations_pb2_grpc  # noqa: E402
from mfiles import enums_pb2 as enums  # noqa: E402
from mfiles import structs_pb2 as structs  # noqa: E402

# Bytes per DownloadFile_ReadBlock call; the server allows at most 4 MiB.
BLOCK_SIZE = 4 * 1024 * 1024


def main() -> None:
    item_id = int_arg(0, 437)
    folder = Path(string_arg(1, str(Path(tempfile.gettempdir()) / "mfiles-grpc-samples")))
    folder.mkdir(parents=True, exist_ok=True)
    with connect() as client:
        objects = client.stub(IRPCObjectOperations_pb2_grpc.IRPCObjectOperationsStub)

        # The latest version lists the files.
        latest = objects.GetObjectInfoAndProperties(ops.GetObjectInfoAndPropertiesRequest(
            obj_id=obj_id(0, item_id), latest_version=True)).object_info
        print(describe(latest))

        for file in latest.version_info.files:
            name = f"{file.title}.{file.extension}"
            # A title can contain characters that are not allowed in file names, such as "/".
            path = folder / re.sub(r'[<>:"/\\|?*]', "_", name)
            size = download(objects, latest.object_info.obj_id, file.file_ver, path)
            print(f"  Downloaded {name} ({size} bytes) to {path}")


# [START download-file]
def download(objects, object_id: structs.ObjID, file_ver: structs.FileVer, path: Path) -> int:
    """Downloads one file version in blocks: DownloadFile_Begin opens a download session,
    DownloadFile_ReadBlock reads it, DownloadFile_Stop closes it."""
    begin = objects.DownloadFile_Begin(ops.DownloadFile_BeginRequest(
        obj_file_ver=structs.ObjFileVer(obj_id=object_id, file_ver=file_ver),
        format=enums.FILE_FORMAT_NATIVE,
        prepare_file_for_downloading=True))
    try:
        size = begin.file_outline.size
        with open(path, "wb") as output:
            offset = 0
            while offset < size:
                block = objects.DownloadFile_ReadBlock(ops.DownloadFile_ReadBlockRequest(
                    download_id=begin.download_id,
                    block_size=min(BLOCK_SIZE, size - offset),
                    offset=offset))
                if not block.data:
                    raise IOError(f"The server returned no data at offset {offset} of {size}.")
                output.write(block.data)
                offset += len(block.data)
        return size
    finally:
        objects.DownloadFile_Stop(ops.DownloadFile_StopRequest(download_id=begin.download_id))
# [END download-file]


if __name__ == "__main__":
    run(main)
