import ast
import base64
import itertools

from functools import lru_cache

import cpapilib

from flask import session

from app import app


OBJECTS_DICTIONARY = None


@lru_cache(maxsize=5000)
def uid_name(uid_obj):
    for obj in OBJECTS_DICTIONARY:
        if uid_obj == obj['uid']:
            return obj['name']


class API(cpapilib.Management):

    def pre_data(self):
        self.all_colors = [
                'aquamarine', 'blue', 'crete blue', 'burlywood', 'cyan',
                'dark green', 'khaki', 'orchid', 'dark orange', 'dark sea green',
                'pink', 'turquoise', 'dark blue', 'firebrick', 'brown',
                'forest green', 'gold', 'dark gold', 'gray', 'dark gray',
                'light green', 'lemon chiffon', 'coral', 'sea green', 'sky blue',
                'magenta', 'purple', 'slate blue', 'violet red', 'navy blue',
                'olive', 'orange', 'red', 'sienna', 'yellow'
            ]
        self.all_commands = [command['name'] for command in self.show('commands')['commands']]
        self.all_targets = [target['name'] for batch in self.show_all('gateways-and-servers') for target in batch['objects']]
        self.all_layers = [(layer['name'], layer['uid']) for batch in self.show_all('access-layer') for layer in batch['access-layers']]

    def customcommand(self, command, payload):
        """Validate payload and send command to server."""
        try:
            payload = ast.literal_eval(payload)
        except ValueError:
            return 'Invalid input provided.'
        except Exception as e:
            return e
        return self._api_call(command, **payload)

    def runcommand(self, targets, script):
        """Issue command against Check Point targets, verify task is complete
        on each gateways and return response for each target."""
        taskreturn = []
        payload = {
            'script-name': 'cpapi',
            'script': script,
            'targets': targets
        }
        response = self.run('script', **payload)
        if 'tasks' in response:
            for task in response['tasks']:
                target = task['target']
                taskid = task['task-id']
                taskresponse = self.monitortask(target, taskid)
                taskreturn.append(taskresponse)
        return taskreturn

    @staticmethod
    def base64_ascii(base64resp):
        """Converts base64 to ascii for run command/showtask."""
        return base64.b64decode(base64resp).decode('utf-8')

    def monitortask(self, target, taskid):
        """Run gettask until task is complete and we can return response."""
        if self.monitor_task(taskid, timeout=30):
            response = self.show('task', **{'task-id': taskid, 'details-level': 'full'})
            if response['tasks'][0]['task-details'][0]['responseMessage']:
                base64resp = response['tasks'][0]['task-details'][0]['responseMessage']
                asciiresp = self.base64_ascii(base64resp)
                taskresponse = {
                    'target': target,
                    'status': response['tasks'][0]['status'],
                    'response': asciiresp
                }
            else:
                taskresponse = {
                    'target': target,
                    'status': response['tasks'][0]['status'],
                    'response': 'Not Available'
                }
        else:
            app.logger.warn('Script did not finish within time limit on {}.'.format(target))
            taskresponse = {
                'target': target,
                'status': 'Task did not complete within 30 seconds.',
                'response': 'Unavailable.'
            }
        return taskresponse

    def show_object(self, objuid):
        show_obj_response = self.show('object', uid=objuid)
        payload = {
            'uid': objuid,
            'details-level': 'full'
        }
        type_obj_response = self.show(show_obj_response['object']['type'], **payload)
        return type_obj_response

    def show_rules(self, **kwargs):
        """Recieves Layer UID, limit, offset."""
        all_rules = {'rulebase': []}
        app.logger.info('Retrieving rules for - {}'.format(kwargs))
        response = self.show('access-rulebase', **kwargs)
        all_rules.update({'to': response['to'], 'total': response['total']})
        self._filter_rules(all_rules, response)
        return all_rules

    def _filter_rules(self, all_rules, response):
        """Recieves show_rules response and performs logic against whether
        rules are sections or rules."""
        for rule in response['rulebase']:
            if 'type' in rule:
                if rule['type'] == 'access-rule':
                    final = self._filter_rule(rule, response['objects-dictionary'])
                    all_rules['rulebase'].append(final)
                elif rule['type'] == 'access-section':
                    if 'name' in rule:
                        section = rule['name']
                    else:
                        section = ''
                    all_rules['rulebase'].append({'type': 'accesssection', 'name': section})
            if 'rulebase' in rule:
                for subrule in rule['rulebase']:
                    final = self._filter_rule(subrule, response['objects-dictionary'])
                    all_rules['rulebase'].append(final)
        return all_rules

    @staticmethod
    def _filter_rule(rule, object_dictionary):
        """Recieves rule and replaces UID with Name."""
        global OBJECTS_DICTIONARY
        OBJECTS_DICTIONARY = object_dictionary
        src = rule['source']
        src_all = []
        dst = rule['destination']
        dst_all = []
        srv = rule['service']
        srv_all = []
        act = rule['action']
        trg = rule['install-on']
        trg_all = []
        if rule['track']['type']:
            trc = rule['track']['type']
        else:
            trc = rule['track']
        for srcobj, dstobj, srvobj, trgobj in itertools.zip_longest(src, dst, srv, trg):
            if srcobj:
                src_all.append((uid_name(srcobj), srcobj))
            if dstobj:
                dst_all.append((uid_name(dstobj), dstobj))
            if srvobj:
                srv_all.append((uid_name(srvobj), srvobj))
            if trgobj:
                trg_all.append((uid_name(trgobj), trgobj))
        return {
            'type': 'accessrule',
            'number': rule['rule-number'],
            'name': rule.get('name', ''),
            'source': src_all,
            'source-negate': rule['source-negate'],
            'destination': dst_all,
            'destination-negate': rule['destination-negate'],
            'service': srv_all,
            'service-negate': rule['service-negate'],
            'action': uid_name(act),
            'track': uid_name(trc),
            'target': trg_all,
            'enabled': rule['enabled']
        }
