# AUTOGENERATED! DO NOT EDIT! File to edit: ../../../pts/mod/_utils/02_sync_helper.pct.py.

# %% top_export
from pathlib import Path
import subprocess
from typing import Literal
from enum import Enum
import inspect
from repoyard._utils import check_interrupted, enable_soft_interruption, SoftInterruption

from repoyard import const

# %% top_export
from repoyard._models import SyncStatus

class SyncSetting(Enum):
    CAREFUL = "careful"
    REPLACE = "replace"
    FORCE = "force"

class SyncDirection(Enum):
    PUSH = "push" # local -> remote
    PULL = "pull" # remote -> local

# %% top_export
class SyncFailed(Exception): pass
class SyncUnsafe(Exception): pass
class InvalidRemotePath(Exception): pass

async def sync_helper(
    rclone_config_path: str,
    sync_direction: SyncDirection|None, # None = auto
    sync_setting: SyncSetting,
    local_path: str,
    local_sync_record_path: str,
    remote: str,
    remote_path: str,
    remote_sync_record_path: str,
    local_sync_backups_path: str,
    remote_sync_backups_path: str,
    include_path: Path|None = None,
    exclude_path: Path|None = None,
    filters_path: Path|None = None,
    include: list[str]|None = None,
    exclude: list[str]|None = None,
    filter: list[str]|None = None,
    delete_backup: bool = True,
    creator_hostname: str|None = None,
    verbose: bool = False,
    show_rclone_progress: bool = False,
) -> tuple[SyncStatus, bool]:
    """
    Helper to execute the standard routine for syncing a local and remote folder.

    Returns a tuple of the sync status and a boolean indicating if the sync took place.
    """
    
    # AUTOGENERATED! DO NOT EDIT! File to edit: ../../../pts/mod/_utils/02_sync_helper.pct.py.
    
    # %% auto 0
    __all__ = ['sync_status', 'sync_condition', 'local_path_exists', 'remote_path_exists', 'local_sync_record', 'remote_sync_record',
               'sync_path_is_dir', 'rec', 'backup_name']
    
    # %% ../../../pts/mod/_utils/02_sync_helper.pct.py 14
    if not remote_path:
        raise InvalidRemotePath("Remote path cannot be empty.") # Disqualifying empty remote paths as it can cause issues with the safety mechanisms
    
    # %% ../../../pts/mod/_utils/02_sync_helper.pct.py 15
    if sync_direction is None and sync_setting != SyncSetting.CAREFUL:
        raise ValueError("Auto sync direction can only be used with careful sync setting.")
    
    # %% ../../../pts/mod/_utils/02_sync_helper.pct.py 17
    from .._models import get_sync_status, SyncCondition
    
    sync_status = await get_sync_status(
        rclone_config_path=rclone_config_path,
        local_path=local_path,
        local_sync_record_path=local_sync_record_path,
        remote=remote,
        remote_path=remote_path,
        remote_sync_record_path=remote_sync_record_path,
    )
    sync_condition, local_path_exists, remote_path_exists, local_sync_record, remote_sync_record, sync_path_is_dir = sync_status
    
    # %% ../../../pts/mod/_utils/02_sync_helper.pct.py 19
    def _raise_unsafe():
        raise SyncUnsafe(inspect.cleandoc(f"""
            Sync is unsafe. Info:
                Local exists: {local_path_exists}
                Remote exists: {remote_path_exists}
                Local sync record: {local_sync_record}
                Remote sync record: {remote_sync_record}
                Sync status: {sync_condition.value}
        """))
    
    if sync_setting != SyncSetting.FORCE and sync_condition == SyncCondition.SYNCED:
        if verbose: print("Sync not needed.")
        return sync_status, False #|return_line
    
    if sync_direction is None: # auto
        if sync_condition == SyncCondition.NEEDS_PUSH:
            sync_direction = SyncDirection.PUSH
        elif sync_condition == SyncCondition.NEEDS_PULL:
            sync_direction = SyncDirection.PULL
        elif sync_condition == SyncCondition.SYNC_INCOMPLETE:
            _raise_unsafe()
        else:
            _raise_unsafe() # In the case where the sync status is SYNCED, 'auto'-mode should not reach this, as it should have already returned (as auto can only be used in CAREFUL mode)
    
    if sync_setting == SyncSetting.CAREFUL:
        if sync_direction == SyncDirection.PUSH and sync_condition not in [SyncCondition.NEEDS_PUSH, SyncCondition.SYNCED]:
            _raise_unsafe()
        elif sync_direction == SyncDirection.PULL and sync_condition not in [SyncCondition.NEEDS_PULL, SyncCondition.SYNCED]:
            _raise_unsafe()
    
    # %% ../../../pts/mod/_utils/02_sync_helper.pct.py 21
    from . import rclone_sync, BisyncResult, rclone_mkdir, rclone_path_exists, rclone_purge
        
    async def _sync(
        dry_run: bool,
        source: str,
        source_path: str,
        dest: str,
        dest_path: str,
        backup_remote: str,
        backup_path: str,
        return_command: bool=False,
    ) -> BisyncResult:
        if not sync_path_is_dir:
            dest_path = Path(dest_path).parent.as_posix() # needed because rlcone sync doesn't seem to accept files on the dest path
            if dest_path == '.': dest_path = ''
        
        if verbose:
            print(f"Syncing {source}:{source_path} to {dest}:{dest_path}.  Backup path: {backup_remote}:{backup_path}")
    
        # Create backup store directory if it doesn't already exist
        from repoyard._utils import rclone_mkdir
        await rclone_mkdir(
            rclone_config_path=rclone_config_path,
            source=backup_remote,
            source_path=backup_path,
        )
    
        return await rclone_sync(
            rclone_config_path=rclone_config_path,
            source=source,
            source_path=source_path,
            dest=dest,
            dest_path=dest_path,
            include=include or [],
            exclude=exclude or [],
            filter=filter or [],
            include_file=include_path,
            exclude_file=exclude_path,
            filters_file=filters_path,
            backup_path=f"{backup_remote}:{backup_path}" if backup_remote else backup_path,
            dry_run=dry_run,
            return_command=return_command,
            verbose=False,
            progress=show_rclone_progress,
        )
    
    # %% ../../../pts/mod/_utils/02_sync_helper.pct.py 23
    from .._models import SyncRecord
    
    if check_interrupted(): raise SoftInterruption()
    
    rec = SyncRecord.create(creator_hostname=creator_hostname, sync_complete=False)
    backup_name = str(rec.ulid)
    
    if sync_direction == SyncDirection.PULL:
        # Save the sync record on local to signify an ongoing sync
        await rec.rclone_save(rclone_config_path, "", local_sync_record_path)
    
        backup_remote = ""
        backup_path = Path(local_sync_backups_path) / backup_name
    
        res, stdout, stderr = await _sync(
            dry_run=False,
            source=remote,
            source_path=remote_path,
            dest="",
            dest_path=local_path,
            backup_remote=backup_remote,
            backup_path=backup_path,
        )
        
        if res:
            # Retrieve the remote sync record and save it locally
            rec = await SyncRecord.rclone_read(rclone_config_path, remote, remote_sync_record_path)
            await rec.rclone_save(rclone_config_path, "", local_sync_record_path)
    
    elif sync_direction == SyncDirection.PUSH:
        # Save the sync record on remote to signify an ongoing sync
        await rec.rclone_save(rclone_config_path, remote, remote_sync_record_path)
    
        backup_remote = remote
        backup_path = Path(remote_sync_backups_path) / backup_name
    
        res, stdout, stderr = await _sync(
            dry_run=False,
            source="",
            source_path=local_path,
            dest=remote,
            dest_path=remote_path,
            backup_remote=backup_remote,
            backup_path=backup_path,
        )
    
        if res:
            # Create a new sync record and save it at the remote
            rec = SyncRecord.create(creator_hostname=creator_hostname, sync_complete=True)
            await rec.rclone_save(rclone_config_path, "", local_sync_record_path)
            await rec.rclone_save(rclone_config_path, remote, remote_sync_record_path)
    
    else:
        raise ValueError(f"Unknown sync direction: {sync_direction}")
    
    if not res:
        raise SyncFailed(f"Sync failed. Rclone output:\n{stdout}\n{stderr}")
    
    if res and delete_backup:
        await rclone_purge(
            rclone_config_path=rclone_config_path,
            source=backup_remote,
            source_path=backup_path,
        )
    return sync_status, True