#!/usr/bin/env python
# -*- coding: utf-8 -*-

import logging
import argparse
import os

from google.protobuf import text_format
from yweb.querydata.querydata_indexer_saas.ferryman.idl import config_pb2

from saas.tools.ssm.SaaSServiceManager import SaaSServiceManager
from saas.tools.ssm.modules.misc import prepare_logging


CONFIG_PATH = 'data/{}/config-{}.pb.txt'
NAMESPACE_TMPL = 'Namespace: "{}"\nMaxDocs: {}\nMaxBytes: {}\nOwners: "{}"\n'
P_NAMESPACE_TMPL = 'Namespace: "{}"\nMaxDocs: {}\nMaxBytes: {}\nPrefix: {}\nOwners: "{}"\n'


def get_configs():
    logging.info('Getting actual configuration')
    config_path = 'svn+ssh://arcadia.yandex.ru/arc/trunk/arcadia/yweb/querydata/querydata_indexer_saas/ferryman/static_configs/data'
    return os.system('svn co {} data'.format(config_path))


def commit_config(config_path, commit_message):
    logging.info('Commiting changes for file %s with message: %s', config_path, commit_message)
    os.system('svn diff {}'.format(config_path))
    return os.system('svn ci -m "{}" {}'.format(commit_message, config_path))


def read_config(path):
    with open(path) as conf:
        parsed_config = text_format.Parse(conf.read(), config_pb2.TConfig())
    return parsed_config


def write_config(path, config_data):
    with open(path, 'w+') as conf:
        conf.write(text_format.MessageToString(config_data))


def parse_config(config_data):
    return text_format.Parse(config_data, config_pb2.TConfig())


class FerrymanConfig(object):
    """
    Ferryman configuration class
    """
    def __init__(self, ferryman_name, ferryman_ctype):
        self.__name = ferryman_name
        self.__ctype = ferryman_ctype
        self.__config_path = CONFIG_PATH.format(self.__ctype, self.__name)
        self.__config_data = read_config(self.__config_path)

    def get_namespace_by_name(self, name):
        for namespace in self.__config_data.DataLimits.Namespaces:
            if name == namespace.Namespace:
                return namespace
        return None

    def get_namespaces(self):
        result = []
        logging.info('Getting namespaces for service %s', self.__name)
        for namespace in self.__config_data.DataLimits.Namespaces:
            ns_data = {
                'Namespace': namespace.Namespace,
                'MaxDocs': namespace.MaxDocs,
                'MaxBytes': namespace.MaxBytes
            }
            result.append(ns_data)
        return result

    def modify_namespace(self, name, doc_count=None, bytes_size=None, ticket=None, owners=None, is_prefix=False):
        ns = self.get_namespace_by_name(name)
        if ns:
            if not doc_count and not bytes_size:
                logging.error('You should specify doc_count or bytes_size parameters')
            else:
                if doc_count:
                    ns.MaxDocs = int(doc_count)
                if bytes_size:
                    ns.MaxBytes = int(bytes_size)
                if is_prefix:
                    ns.Prefix = True
                if owners:
                    if type(owners) == list:
                        owners = ','.join(owners)
                    ns.Owners = str(owners)
                logging.info('Modify namespace %s with attrs: MaxDocs: %s, MaxBytes: %s', name, ns.MaxDocs, ns.MaxBytes)
                write_config(self.__config_path, self.__config_data)
                commit_message = 'Change attrs of namespace {}'.format(name)
                if ticket:
                    commit_message = '{} ({})'.format(commit_message, ticket)
                return commit_message
        else:
            logging.error('Namespace %s was not found', name)

    def add_namespace(self, name, doc_count, bytes_size, owners, ticket=None, is_prefix=False):
        ns = self.get_namespace_by_name(name)
        if ns:
            logging.warning('Namespace %s has already exists. Will override DocCount from %s to %s and MaxBytes from %s to %s',
                            name, ns.MaxDocs, doc_count, ns.MaxBytes, bytes_size)
            return self.modify_namespace(name, doc_count, bytes_size, ticket=ticket, owners=owners, is_prefix=is_prefix)
        else:
            logging.info('Adding namespace %s, with attrs MaxDocs: %s, MaxBytes: %s', name, doc_count, bytes_size)
            if is_prefix:
                ns_text = P_NAMESPACE_TMPL.format(name, doc_count, bytes_size, is_prefix, owners)
            else:
                ns_text = NAMESPACE_TMPL.format(name, doc_count, bytes_size, owners)

            new_namespace = text_format.Parse(ns_text, config_pb2.TNamespaceLimit())
            self.__config_data.DataLimits.Namespaces.extend([new_namespace])
            write_config(self.__config_path, self.__config_data)
            commit_message = 'New namespace {} with attrs: doc_count - {}, max_size - {}'.format(name, doc_count, bytes_size)
            if ticket:
                commit_message = '{} ({})'.format(commit_message, ticket)
            return commit_message

    def remove_namespace(self, name):
        """
        There is a difficult sequence of actions.
        1) Send empty table to namespace and wait for batch
        2) When batch with batch_id is searchable we can set 0 to namespace attrs
        3) When namespace fade out from /get_namespaces handle we can remove namespace from config
        """
        pass

    def change_input_size(self, new_size):
        self.__config_data.AllowedInputSize = new_size
        write_config(self.__config_path, self.__config_data)

    def commit_changes(self, message, config_path=None):
        if not config_path:
            config_path = self.__config_path
        logging.info('Committing changes for path: %s with message: %s', config_path, message)
        return commit_config(config_path, message)


def parse_args(*args):
    options = argparse.ArgumentParser(prog='ssm_namespaces',
                                      formatter_class=argparse.RawTextHelpFormatter,
                                      description="""
DESCRIPTION
Tool for SaaS Ferryman config and namespace management
Ferryman documentation: https://wiki.yandex-team.ru/jandekspoisk/SaaS/Ferryman/
DevOps documentation: https://wiki.yandex-team.ru/jandekspoisk/saas/ferryman/devopsing/


Example:
%(prog)s --ctype prestable --count 100000 --ns-name new_namespace --size 100000000
""")

    options.add_argument('service_name', type=str,
                         help='Specifies service name for ferryman creation')
    options.add_argument('--ctype', dest='service_ctype', required=True, choices=SaaSServiceManager.ctype_tags.keys(),
                         help='Sets ctype of service')
    options.add_argument('--ns-name', dest='namespace_name', required=True,
                         help='Specify namespace name')
    options.add_argument('-c', '--count', dest='max_docs', type=int,
                         help='Specifies MaxDocs parameter')
    options.add_argument('--ns-size', dest='namespace_size', type=int,
                         help='Specifies allowed input size')
    options.add_argument('--ns-owners', dest='namespace_owners', type=str,
                         help='Specify namespace owners')
    options.add_argument('--ticket', dest='startrek_ticket',
                         help='Specify startrek ticket for commit message')
    options.add_argument('--dry-run', dest='dry_run', default=False, action='store_true',
                         help='Only local changes without commit')
    options.add_argument('--is-prefix', dest='is_prefix', default=False, action='store_true',
                         help='Specify namespace as prefix')
    options.add_argument('--debug', dest='debug', action='store_true', default=False,
                         help='Enable debug mode with additional information')

    if args:
        opts = options.parse_args(args)
    else:
        opts = options.parse_args()
    return opts


def main(*args):
    options = parse_args(*args)
    # Logging
    prepare_logging(options)
    get_configs()

    ferryman_config_client = FerrymanConfig(options.service_name, options.service_ctype)
    if (not options.max_docs and options.namespace_size) or (not options.namespace_size and options.max_docs):
        commit_message = ferryman_config_client.modify_namespace(options.namespace_name, doc_count=options.max_docs,
                                                                 bytes_size=options.namespace_size, ticket=options.startrek_ticket,
                                                                 owners=options.namespace_owners,
                                                                 is_prefix=options.is_prefix)
    elif options.max_docs and options.namespace_size and options.namespace_owners:
        commit_message = ferryman_config_client.add_namespace(options.namespace_name, options.max_docs, options.namespace_size,
                                                              options.namespace_owners,
                                                              ticket=options.startrek_ticket, is_prefix=options.is_prefix)
    else:
        commit_message = None

    if not options.dry_run and commit_message:
        ferryman_config_client.commit_changes(commit_message)


if __name__ == '__main__':
    main()
