import demistomock as demisto
from CommonServerPython import *
''' IMPORTS '''
import base64
import json
import time
import devodsconnector as ds
import concurrent.futures
import tempfile
import urllib.parse
import re
import os
from datetime import datetime
from devo.sender import Lookup, SenderConfigSSL, Sender
from typing import List, Dict, Set
from devodsconnector import error_checking
from functools import partial

''' GLOBAL VARS '''
ALLOW_INSECURE = demisto.params().get('insecure', False)
READER_ENDPOINT = demisto.params().get('reader_endpoint', None)
READER_OAUTH_TOKEN = demisto.params().get('reader_oauth_token', None)
WRITER_RELAY = demisto.params().get('writer_relay', None)
WRITER_CREDENTIALS = demisto.params().get('writer_credentials', None)
LINQ_LINK_BASE = demisto.params().get('linq_link_base', "https://us.devo.com/welcome")
FETCH_INCIDENTS_FILTER = demisto.params().get('fetch_incidents_filters', None)
FETCH_INCIDENTS_DEDUPE = demisto.params().get('fetch_incidents_deduplication', None)
TIMEOUT = demisto.params().get('timeout', '60')
PORT = arg_to_number(demisto.params().get('port', '443') or '443')
HEALTHCHECK_WRITER_RECORD = [{'hello': 'world', 'from': 'demisto-integration'}]
HEALTHCHECK_WRITER_TABLE = 'test.keep.free'
RANGE_PATTERN = re.compile('^[0-9]+ [a-zA-Z]+')
TIMESTAMP_PATTERN = re.compile('^[0-9]+')
TIMESTAMP_PATTERN_MILLI = re.compile('^[0-9]+.[0-9]+')
ALERTS_QUERY = '''
from
    siem.logtrust.alert.info
select
    eventdate,
    alertHost,
    domain,
    priority,
    context,
    category,
    status,
    alertId,
    srcIp,
    srcPort,
    srcHost,
    dstIp,
    dstPort,
    dstHost,
    application,
    engine,
    extraData
'''
HEALTHCHECK_QUERY = '''
from
    test.keep.free
select
    *
'''
SEVERITY_LEVELS_MAP = {
    '1': 0.5,
    '2': 1,
    '3': 2,
    '4': 3,
    '5': 4,
    'informational': 0.5,
    'low': 1,
    'medium': 2,
    'high': 3,
    'critical': 4
}


''' HELPER FUNCTIONS '''


def alert_to_incident(alert):
    alert_severity = float(1)
    alert_name = alert['context'].split('.')[-1]
    alert_description = None
    alert_occurred = demisto_ISO(float(alert['eventdate']) / 1000)
    alert_labels = []

    if demisto.get(alert['extraData'], 'alertPriority'):
        alert_severity = SEVERITY_LEVELS_MAP[str(alert['extraData']['alertPriority']).lower()]

    if demisto.get(alert['extraData'], 'alertName'):
        alert_name = alert['extraData']['alertName']

    if demisto.get(alert['extraData'], 'alertDescription'):
        alert_description = alert['extraData']['alertDescription']

    new_alert: Dict = {
        'devo.metadata.alert': {}
    }
    for key in alert:
        if key == 'extraData':
            continue
        new_alert['devo.metadata.alert'][key] = alert[key]
        alert_labels.append({'type': f'devo.metadata.alert.{key}', 'value': str(alert[key])})

    for key in alert['extraData']:
        new_alert[key] = alert['extraData'][key]
        alert_labels.append({'type': f'{key}', 'value': str(alert['extraData'][key])})

    incident = {
        'name': alert_name,
        'severity': alert_severity,
        'details': alert_description,
        'occurred': alert_occurred,
        'labels': alert_labels,
        'rawJSON': json.dumps(new_alert)
    }

    return incident


# Monkey patching for backwards compatibility
def get_types(self, linq_query, start, ts_format):
    type_map = self._make_type_map(ts_format)
    stop = self._to_unix(start)
    start = stop - 1

    response = self._query(linq_query, start=start, stop=stop, mode='json/compact', limit=1)

    try:
        data = json.loads(response)
        error_checking.check_status(data)
    except ValueError:
        raise Exception('API V2 response error')

    col_data = data['object']['m']
    type_dict = {c: type_map[v['type']] for c, v in col_data.items()}

    return type_dict


def build_link(query, start_ts_milli, end_ts_milli, mode='loxcope', linq_base=None):
    myb64str = base64.b64encode((json.dumps({
        'query': query,
        'mode': mode,
        'dates': {
            'from': start_ts_milli,
            'to': end_ts_milli
        }
    }).encode('ascii'))).decode()

    if linq_base:
        url = linq_base + f"/#/vapps/app.custom.queryApp_dev?&targetQuery={myb64str}"
    else:
        url = LINQ_LINK_BASE + f"/#/vapps/app.custom.queryApp_dev?&targetQuery={myb64str}"

    return url


def check_configuration():
    # Check all settings related if set
    # Basic functionality of integration
    list(ds.Reader(oauth_token=READER_OAUTH_TOKEN, end_point=READER_ENDPOINT, verify=not ALLOW_INSECURE)
         .query(HEALTHCHECK_QUERY, start=int(time.time() - 1), stop=int(time.time()), output='dict'))

    if WRITER_RELAY and WRITER_CREDENTIALS:
        creds = get_writer_creds()
        Sender(SenderConfigSSL(address=(WRITER_RELAY, PORT),
                               key=creds['key'].name, cert=creds['crt'].name, chain=creds['chain'].name))\
            .send(tag=HEALTHCHECK_WRITER_TABLE, msg=f'{HEALTHCHECK_WRITER_RECORD}')

    if FETCH_INCIDENTS_FILTER:
        alert_filters = check_type(FETCH_INCIDENTS_FILTER, dict)

        assert alert_filters['type'] in ['AND', 'OR'], 'Missing key:"type" or unsupported value in fetch_incidents_filters'

        filters = check_type(alert_filters['filters'], list)

        for filt in filters:
            assert filt['key'], 'Missing key: "key" in fetch_incidents_filters.filters configuration'
            assert filt['operator'] in ['=', '/=', '>', '<', '>=', '<=', 'and', 'or', '->'], 'Missing key: "operator"'\
                ' or unsupported operator in fetch_incidents_filters.filters configuration'
            assert filt['value'], 'Missing key:"value" in fetch_incidents_filters.filters configuration'

    if FETCH_INCIDENTS_DEDUPE:
        dedupe_conf = check_type(FETCH_INCIDENTS_DEDUPE, dict)

        assert isinstance(dedupe_conf['cooldown'], (int, float)), 'Invalid fetch_incidents_deduplication configuration'

    return True


def check_type(input, tar_type):
    if isinstance(input, str):
        input = json.loads(input)
        if not isinstance(input, tar_type):
            raise ValueError(f'tables to query should either be a json string of a {tar_type} or a {tar_type} input')
    elif isinstance(input, tar_type):
        pass
    else:
        raise ValueError(f'tables to query should either be a json string of a {tar_type} or a {tar_type} input')
    return input


# Converts epoch (miliseconds) to ISO string
def demisto_ISO(s_epoch):
    if s_epoch >= 0:
        return datetime.utcfromtimestamp(s_epoch).strftime("%Y-%m-%dT%H:%M:%S.%fZ")
    return s_epoch


# We will assume timestamp_from and timestamp_to will be the same format or to will be None
def get_time_range(timestamp_from, timestamp_to):
    if isinstance(timestamp_from, (int, float)):
        t_from = timestamp_from
        if timestamp_to is None:
            t_to = time.time()
        else:
            t_to = timestamp_to
    elif isinstance(timestamp_from, str):
        if re.fullmatch(RANGE_PATTERN, timestamp_from):
            t_range = parse_date_range(timestamp_from)
            t_from = t_range[0].timestamp()
            t_to = t_range[1].timestamp()
        elif re.fullmatch(TIMESTAMP_PATTERN, timestamp_from) or re.fullmatch(TIMESTAMP_PATTERN_MILLI, timestamp_from):
            t_from = float(timestamp_from)
            if timestamp_to is None:
                t_to = time.time()
            else:
                t_to = float(timestamp_to)
        else:
            t_from = date_to_timestamp(timestamp_from) / 1000
            if timestamp_to is None:
                t_to = time.time()
            else:
                t_to = date_to_timestamp(timestamp_to) / 1000
    elif isinstance(timestamp_from, datetime):
        t_from = timestamp_from.timestamp()
        if timestamp_to is None:
            t_to = time.time()
        else: