######################################################################
#
# File: b2sdk/v0/sync.py
#
# Copyright 2019 Backblaze Inc. All Rights Reserved.
#
# License https://www.backblaze.com/using_b2_code.html
#
######################################################################
from __future__ import annotations

import logging

from b2sdk.v1.exception import CommandError
from b2sdk.v1.exception import DestFileNewer as DestFileNewerV1
from b2sdk.v1 import trace_call
from .exception import DestFileNewer
from b2sdk.v1.exception import InvalidArgument, IncompleteSync
from b2sdk.v1 import NewerFileSyncMode, CompareVersionMode
from b2sdk.v1 import KeepOrDeleteMode
from b2sdk.v1 import DEFAULT_SCAN_MANAGER
from b2sdk.v1 import SyncReport
from b2sdk.v1 import Synchronizer as SynchronizerV1
from b2sdk.v1 import (
    AbstractSyncEncryptionSettingsProvider,
    SERVER_DEFAULT_SYNC_ENCRYPTION_SETTINGS_PROVIDER,
)

logger = logging.getLogger(__name__)


class Synchronizer(SynchronizerV1):
    """
    This is wrapper for newer version and will return the v0 style exceptions
    """

    def __init__(self, *args, **kwargs):
        try:
            super().__init__(*args, **kwargs)
        except InvalidArgument as e:
            raise CommandError(f'--{e.parameter_name} {e.message}')

    def _make_file_sync_actions(self, *args, **kwargs):
        try:
            yield from super()._make_file_sync_actions(*args, **kwargs)
        except DestFileNewerV1 as e:
            raise DestFileNewer(e.dest_file, e.source_file, e.dest_prefix, e.source_prefix)

    def sync_folders(self, *args, **kwargs):
        try:
            super().sync_folders(*args, **kwargs)
        except InvalidArgument as e:
            raise CommandError(f'--{e.parameter_name} {e.message}')
        except IncompleteSync as e:
            raise CommandError(str(e))


def get_synchronizer_from_args(
    args,
    max_workers,
    policies_manager=DEFAULT_SCAN_MANAGER,
    dry_run=False,
    allow_empty_source=False,
):
    if args.replaceNewer and args.skipNewer:
        raise CommandError('--skipNewer and --replaceNewer are incompatible')
    elif args.replaceNewer:
        newer_file_mode = NewerFileSyncMode.REPLACE
    elif args.skipNewer:
        newer_file_mode = NewerFileSyncMode.SKIP
    else:
        newer_file_mode = NewerFileSyncMode.RAISE_ERROR

    if args.delete and (args.keepDays is not None):
        raise CommandError('--delete and --keepDays are incompatible')

    if args.compareVersions == 'none':
        compare_version_mode = CompareVersionMode.NONE
    elif args.compareVersions == 'modTime':
        compare_version_mode = CompareVersionMode.MODTIME
    elif args.compareVersions == 'size':
        compare_version_mode = CompareVersionMode.SIZE
    elif args.compareVersions is None:
        compare_version_mode = CompareVersionMode.MODTIME
    else:
        raise CommandError('Invalid option for --compareVersions')
    compare_threshold = args.compareThreshold

    keep_days = None

    if args.delete:
        keep_days_or_delete = KeepOrDeleteMode.DELETE
    elif args.keepDays:
        keep_days_or_delete = KeepOrDeleteMode.KEEP_BEFORE_DELETE
        keep_days = args.keepDays
    else:
        keep_days_or_delete = KeepOrDeleteMode.NO_DELETE

    return Synchronizer(
        max_workers,
        policies_manager=policies_manager,
        dry_run=dry_run,
        allow_empty_source=allow_empty_source,
        newer_file_mode=newer_file_mode,
        keep_days_or_delete=keep_days_or_delete,
        compare_version_mode=compare_version_mode,
        compare_threshold=compare_threshold,
        keep_days=keep_days,
    )


def make_folder_sync_actions(
    source_folder,
    dest_folder,
    args,
    now_millis,
    reporter,
    policies_manager=DEFAULT_SCAN_MANAGER,
    encryption_settings_provider: AbstractSyncEncryptionSettingsProvider = SERVER_DEFAULT_SYNC_ENCRYPTION_SETTINGS_PROVIDER,
):
    """
    This is deprecated. Use the new Synchronizer class.
    Yields a sequence of actions that will sync the destination
    folder to the source folder.

    :param source_folder: source folder object
    :type source_folder: b2sdk._internal.scan.folder.AbstractFolder
    :param dest_folder: destination folder object
    :type dest_folder: b2sdk._internal.scan.folder.AbstractFolder
    :param args: an object which holds command line arguments
    :param now_millis: current time in milliseconds
    :type now_millis: int
    :param reporter: reporter object
    :param policies_manager: policies manager object
    :param encryption_settings_provider: encryption settings provider
    :type encryption_settings_provider: AbstractSyncEncryptionSettingsProvider
    """
    synchronizer = get_synchronizer_from_args(
        args,
        1,
        policies_manager=policies_manager,
        dry_run=False,
        allow_empty_source=False,
    )
    try:
        return synchronizer.make_folder_sync_actions(
            source_folder,
            dest_folder,
            now_millis,
            reporter,
            policies_manager=policies_manager,
            encryption_settings_provider=encryption_settings_provider,
        )
    except InvalidArgument as e:
        raise CommandError(f'--{e.parameter_name} {e.message}')


@trace_call(logger)
def sync_folders(
    source_folder,
    dest_folder,
    args,
    now_millis,
    stdout,
    no_progress,
    max_workers,
    policies_manager=DEFAULT_SCAN_MANAGER,
    dry_run=False,
    allow_empty_source=False,
    encryption_settings_provider: AbstractSyncEncryptionSettingsProvider = SERVER_DEFAULT_SYNC_ENCRYPTION_SETTINGS_PROVIDER,
):
    """
    This is deprecated. Use the new Synchronizer class.

    source is also in the destination.  Deletes any file versions
    in the destination older than history_days.

    :param source_folder: source folder object
    :type source_folder: b2sdk._internal.scan.folder.AbstractFolder
    :param dest_folder: destination folder object
    :type dest_folder: b2sdk._internal.scan.folder.AbstractFolder
    :param args: an object which holds command line arguments
    :param now_millis: current time in milliseconds
    :type now_millis: int
    :param stdout: standard output file object
    :param no_progress: if True, do not show progress
    :type no_progress: bool
    :param max_workers: max number of workers
    :type max_workers: int
    :param policies_manager: policies manager object
    :param dry_run:
    :type dry_run: bool
    :param allow_empty_source: if True, do not check whether source folder is empty
    :type allow_empty_source: bool
    :param encryption_settings_provider: encryption settings provider
    :type encryption_settings_provider: AbstractSyncEncryptionSettingsProvider
    """
    synchronizer = get_synchronizer_from_args(
        args,
        max_workers,
        policies_manager=policies_manager,
        dry_run=dry_run,
        allow_empty_source=allow_empty_source,
    )
    with SyncReport(stdout, no_progress) as reporter:
        synchronizer.sync_folders(
            source_folder,
            dest_folder,
            now_millis,
            reporter,
            encryption_settings_provider=encryption_settings_provider,
        )
