# -*- coding: utf-8 -*-
from datetime import datetime
from yt.wrapper import ypath_join, ypath_dirname
from yt.transfer_manager.client import TransferManager

from datacloud.dev_utils.logging.logger import get_basic_logger
from datacloud.dev_utils.yt.yt_utils import get_yt_client
from datacloud.dev_utils.yt.yt_config_table import ConfigTable
from datacloud.dev_utils.yt.yt_ops import compress_table, get_tables, YT_TABLE_TYPE, YT_DIR_TYPE
from datacloud.dev_utils.time.patterns import FMT_DATE_HM, FMT_DATE
from datacloud.dev_utils.status_db.task import Task, Status
from datacloud.dev_utils.time.utils import now_str
from datacloud.config.yt import YT_PROXY

logger = get_basic_logger()

BACKUP_FOLDER = '//home/x-products/backups'

PATHS_TO_BACKUP_TABLE_PATH = '//home/x-products/production/paths_to_backup'
PATHS_TO_BACKUP_TABLE_SCHEMA = [
    {'name': 'path_to_backup', 'type': 'string', 'sort_order': 'ascending'},
    {'name': 'backup_node_name', 'type': 'string'},
    {'name': 'days_between_backups', 'type': 'uint32'},
    {'name': 'backups_to_store', 'type': 'uint32'},
]


DELIMITER = '#'
EPOCH_START = datetime(1970, 1, 1)
BACKUP_CLUSTER = 'arnold'
BACKUP_STORAGE_COMPRESSION = 'brotli_6'
BACKUP_STORAGE_ERASURE = 'lrc_12_2_2'
TRANSFER_LEASE_TIMEOUT = 60 * 60  # one hour


def get_paths_to_backup_table(yt_client):
    return ConfigTable(
        PATHS_TO_BACKUP_TABLE_PATH,
        PATHS_TO_BACKUP_TABLE_SCHEMA,
        yt_client=yt_client
    )


def get_all_paths_to_backup(yt_client):
    paths_to_backup_table = get_paths_to_backup_table(yt_client)
    return paths_to_backup_table.list_records()


def get_backup_settings(path, yt_client):
    paths_to_backup_table = get_paths_to_backup_table(yt_client)
    return paths_to_backup_table.get_record_by_params({'path_to_backup': path})


def get_backup_path(path, date_str, yt_client):
    row = get_backup_settings(path, yt_client)
    backup_node_name = row['backup_node_name']
    return ypath_join(BACKUP_FOLDER, backup_node_name + '_{}'.format(date_str))


def detect_backup_ready(date_time, days=None):
    yt_client = get_yt_client()

    dt = datetime.strptime(date_time, FMT_DATE_HM)
    days_since_epoch = (dt - EPOCH_START).days
    date_str = dt.strftime(FMT_DATE)

    for row in get_all_paths_to_backup(yt_client):
        path_to_backup = row['path_to_backup']
        logger.info(' Check path: %s', path_to_backup)
        if days_since_epoch % row['days_between_backups'] == 0:
            path_in_backup = get_backup_path(path_to_backup, date_str, yt_client)
            if not yt_client.exists(path_in_backup):
                logger.info(' %s will be backuped', path_to_backup)
                key = '{}{}{}'.format(path_to_backup, DELIMITER, date_str)
                yield key, {'path': path_to_backup, 'date_str': date_str}
            else:
                logger.info(' Already backuped %s today', path_to_backup)
        else:
            logger.info(' Time hasn\'t come for: %s', path_to_backup)


def delete_oldest_backup(backup_settings, yt_client):
    full_path = ypath_join(BACKUP_FOLDER, backup_settings['backup_node_name'])
    folder_to_look = ypath_dirname(full_path)

    all_nodes = []
    if yt_client.exists(folder_to_look):
        all_nodes = yt_client.list(folder_to_look)
    backup_node_name = full_path.split('/')[-1]

    nodes = filter(lambda x: backup_node_name in x, all_nodes)
    nodes = sorted(nodes)

    path = backup_settings['path_to_backup']
    if len(nodes) > backup_settings['backups_to_store']:
        raise Exception('Too many ({}) backups for {}'.format(len(nodes), path))
    elif len(nodes) == backup_settings['backups_to_store']:
        node_to_delte = ypath_join(folder_to_look, nodes[0])
        logger.info(' Deleting %s', node_to_delte)
        yt_client.remove(node_to_delte, force=True, recursive=True)
    else:
        logger.info(' Too few copies of %s to delete any', path)


def create_dir_if_not_exists(path, yt_client):
    if yt_client.exists(path) and yt_client.get_attribute(path, 'type') == YT_DIR_TYPE:
        logger.info(' %s is dir itself', path)
        dirname = path
    else:
        dirname = ypath_dirname(path)

    if not yt_client.exists(dirname):
        logger.info(' %s doesn\'t exist, will create it', dirname)
        yt_client.mkdir(dirname, recursive=True)


def run_backup(task):
    logger.info(' Backuping {}'.format(task))

    path = task.data['path']
    date_str = task.data['date_str']
    yt_client = get_yt_client()

    backup_settings = get_backup_settings(path, yt_client)
    path_to_backup = get_backup_path(path, date_str, yt_client)

    with yt_client.Transaction():
        delete_oldest_backup(backup_settings, yt_client)
        logger.info(' Copying %s to %s', path, path_to_backup)

        obj_type = yt_client.get_attribute(path, 'type')
        if obj_type == YT_TABLE_TYPE and yt_client.get_attribute(path, 'dynamic'):
            logger.info(' %s is dynamic table. Using map to copy it', path)
            create_dir_if_not_exists(path_to_backup, yt_client)

            def _mapper(rec):
                yield rec
            yt_client.run_map(
                _mapper,
                path,
                path_to_backup,
                spec={
                    'title': '[BACKUP] {}'.format(path)
                },

            )
        else:
            yt_client.copy(path, path_to_backup, recursive=True, force=True)

    current_time = now_str()
    new_tasks = [
        task.make_done(),
        Task('backup_compress', task.key, Status.READY, {'path': path, 'date_str': date_str}, current_time, current_time)
    ]
    return new_tasks


def run_backup_compress(task):
    yt_client = get_yt_client()

    path, date_str = task.data['path'], task.data['date_str']
    path_to_backup = get_backup_path(path, date_str, yt_client)

    logger.info(' Compressing %s', path_to_backup)

    for table in get_tables(path_to_backup, yt_client):
        compress_table(table, title_suffix='[BACKUP] {}'.format(table))

    current_time = now_str()
    new_tasks = [
        task.make_done(),
        Task('backup_transfer', task.key, Status.READY, {'path': path, 'date_str': date_str}, current_time, current_time)
    ]
    return new_tasks


def run_backup_transfer(task):
    yt_client_backup = get_yt_client(BACKUP_CLUSTER)
    yt_client = get_yt_client()

    path, date_str = task.data['path'], task.data['date_str']
    path_to_backup = get_backup_path(path, date_str, yt_client)

    logger.info(' Start transfering %s', path_to_backup)
    create_dir_if_not_exists(BACKUP_FOLDER, yt_client_backup)

    backup_settings = get_backup_settings(path, yt_client)
    delete_oldest_backup(backup_settings, yt_client_backup)

    tm = TransferManager()
    for table in get_tables(path_to_backup, yt_client, get_files=True):
        logger.info(' Adding task to transfer %s', table)
        task_id = tm.add_task(
            YT_PROXY,
            table,
            BACKUP_CLUSTER,
            table,
            params={
                'lease_timeout': TRANSFER_LEASE_TIMEOUT,
                'skip_if_destination_exists': True,
                'destination_compression_codec': BACKUP_STORAGE_COMPRESSION,
                'destination_erasure_codec': BACKUP_STORAGE_ERASURE,
            },
        )
        logger.info(' Started transfer task %s', task_id)
    return [task.make_done()]
