from saas.tools.devops.lib23.service_token import ServiceToken

import sandbox.common.rest as sandbox
from sandbox.common.proxy import OAuth

import difflib
import os
import subprocess
import requests
import json


class Generator(object):
    def __init__(self, yconf_patcher, input_dir, output_dir, environments, create_oxygen_rt, create_oxygen_med):
        self.yconf_patcher = yconf_patcher
        self.input_dir = input_dir
        self.output_dir = output_dir
        self.environments = environments
        self.create_oxygen_rt = create_oxygen_rt
        self.create_oxygen_med = create_oxygen_med

    def get_input_path(self, filename):
        return os.path.join(self.input_dir, filename)

    def get_output_path(self, filename):
        return os.path.join(self.output_dir, filename)

    def apply_patch(self, input_file, patch_file, output_file, input_dir=None):
        if not input_dir:
            input_dir = self.input_dir
        input_file_path = os.path.join(input_dir, input_file)
        subprocess.check_call(
            ['patch', input_file_path, '-i', self.get_input_path(patch_file), '-o', self.get_output_path(output_file)]
        )

    def apply_yconf_patch(self, input_file, patch_file, output_file):
        """
        Apply yconf patch and add output to resource
        """
        subprocess.check_call(
            [self.yconf_patcher, 'patch', '--ignore-prefix', self.get_input_path(input_file), self.get_input_path(patch_file), self.get_output_path(output_file)]
        )

    @staticmethod
    def oxygen_options_filename(env, suffix=""):
        oxygen_options_common = 'OxygenOptions{}.cfg'.format(suffix)
        if env == 'common':
            return oxygen_options_common
        else:
            return '{}-{}'.format(oxygen_options_common, env)

    def patch_oxygen_options(self):
        patch_rt = 'OxygenOptionsRt.cfg.patch'
        patch_med = 'OxygenOptionsMed.cfg.patch'
        for env in self.environments:
            oxygen_options = self.oxygen_options_filename(env)
            oxygen_options_rt = self.oxygen_options_filename(env, "Rt")
            oxygen_options_med = self.oxygen_options_filename(env, "Med")
            if env == 'common':
                self.copy_to_output(oxygen_options)
            else:
                patch = '{}.patch'.format(oxygen_options)
                self.apply_patch(self.oxygen_options_filename('common'), patch, oxygen_options)

            if (self.create_oxygen_rt):
                self.apply_patch(oxygen_options, patch_rt, oxygen_options_rt, input_dir=self.output_dir)
            if (self.create_oxygen_med):
                self.apply_patch(oxygen_options, patch_med, oxygen_options_med, input_dir=self.output_dir)

    def patch_rtyserver_conf(self):
        for env in self.environments:
            if env == 'common':
                self.copy_to_output('rtyserver.conf-{}'.format(env))
            else:
                self.apply_yconf_patch('rtyserver.conf-common', 'rtyserver.conf-{}_patch.json'.format(env), 'rtyserver.conf-{}'.format(env))

    def patch_basesearch_refresh(self):
        for env in self.environments:
            if env == 'common':
                self.copy_to_output('basesearch-refresh')
            else:
                self.apply_yconf_patch('basesearch-refresh', 'basesearch-refresh-{}_patch.json'.format(env), 'basesearch-refresh-{}'.format(env))

    def patch_environment(self):
        for env in self.environments:
            if env == 'common':
                self.copy_to_output('environment')
            else:
                target_file = 'environment-{}'.format(env)
                self.apply_patch('environment', '{}.patch'.format(target_file), target_file)

    def copy_to_output(self, filename):
        subprocess.check_call(['cp', self.get_input_path(filename), self.get_output_path(filename)])

    def run(self):
        self.patch_oxygen_options()
        self.patch_rtyserver_conf()
        self.patch_basesearch_refresh()
        self.patch_environment()


class Checker(object):
    def __init__(self, nanny_services, configs_dir, tmp_dir='__services_configs'):
        self._configs_dir = configs_dir
        self._tmp_dir = tmp_dir

        self._init_dirs()
        self._init_sandbox()
        self._init_configs(nanny_services)

    def _init_dirs(self):
        if not os.path.exists(self._tmp_dir):
            os.mkdir(self._tmp_dir)
        if not os.path.exists(self._get_results_path()):
            os.mkdir(self._get_results_path())

    def _init_sandbox(self):
        sandbox_token = ServiceToken('sandbox').get_token()
        self._sb_client = sandbox.Client(auth=OAuth(sandbox_token))

    def _init_configs(self, nanny_services):
        self._resources = {}
        for service in nanny_services:
            res = self._get_configs_resource(service)
            if res not in self._resources:
                self._resources[res] = []
            self._resources[res].append(service)

        for res in self._resources:
            self._download_resource(res)

    def _get_results_path(self):
        return os.path.join(self._tmp_dir, 'results')

    def _get_resource_path(self, resource_id):
        return os.path.join(self._tmp_dir, str(resource_id))

    def _get_configs_resource(self, service):
        nanny_url = 'https://nanny.yandex-team.ru/v2/services/{service_name}/runtime_attrs/resources/'
        r = requests.get(nanny_url.format(service_name=service), timeout=15)
        data = json.loads(r.content)
        sb_resources = data['content']['sandbox_files']
        configs_resource = None
        for res in sb_resources:
            if res['resource_type'] in ['SAAS_RTYSERVER_CONFIGS_BUNDLE']:
                if configs_resource:
                    raise Exception('more then one config boundle in service: {}'.format(service))
                configs_resource = res['resource_id']
        if configs_resource is None:
            raise Exception('Not found config boundle in service: {}'.format(service))
        return configs_resource

    def _download_resource(self, resource_id):
        path = self._get_resource_path(resource_id)
        data = self._sb_client.resource[resource_id].read()
        skynet_id = data['skynet_id']
        subprocess.check_call(['sky', 'get', '-uw', '-d', path, skynet_id])

    def _get_files_diff(self, old_config_path, config_path):
        if 'rtyserver' not in config_path:
            return
        with open(old_config_path, 'r') as f:
            old_config = f.read()
        with open(config_path, 'r') as f:
            config = f.read()
        diff = difflib.ndiff(old_config.split('\n'), config.split('\n'))
        diff = [d.rstrip('\n') for d in diff]
        result = {
            'full': '\n'.join([d for d in diff]),
            'short': '\n'.join([d for d in diff if d and d[0] in '+-?'])
        }
        if not result['short']:
            return None
        return result

    def _get_resource_changes(self, resource_id):
        diff_files_path = os.path.join(self._get_results_path(), resource_id)
        if not os.path.exists(diff_files_path):
            os.mkdir(diff_files_path)
        resource_configs_path = os.path.join(self._get_resource_path(resource_id), 'configs')
        configs_files = os.listdir(self._configs_dir)
        resources_files = os.listdir(resource_configs_path)
        changes = {
            'resource': resource_id,
            'services': self._resources[resource_id],
            'added': [], 'removed': [], 'changed': {}
        }
        for config_name in configs_files:
            if config_name not in resources_files:
                changes['added'].append(config_name)
            else:
                diff = self._get_files_diff(
                    os.path.join(resource_configs_path, config_name),
                    os.path.join(self._configs_dir, config_name)
                )
                if diff:
                    diff['path'] = os.path.join(diff_files_path, config_name)
                    with open(diff['path'], 'w') as f:
                        f.write(diff['full'])
                    changes['changed'][config_name] = diff

        for config_name in resources_files:
            if config_name not in configs_files:
                changes['removed'].append(config_name)
        return changes

    def get_changes(self):
        changes = []
        for resource in self._resources.keys():
            changes.append(self._get_resource_changes(resource))
        return changes

    def print_changes(self):
        changes = self.get_changes()
        for item in changes:
            print('=' * 75)
            print('Resource #{}'.format(item['resource']))
            print('Services: {}'.format(', '.join(item['services'])))
            if item['added']:
                print('Added:\n\t{}'.format('\n\t'.join(item['added'])))
            if item['removed']:
                print('Removed:\n\t{}'.format('\n\t'.join(item['removed'])))
            print('Changed:')
            for name, diff in item['changed'].items():
                print('-' * 75)
                print('Config name: {}'.format(name))
                print('Full diff path: {}'.format(diff['path']))
                print(diff['short'])
