"""Create the MikroTik dashboard in the Wazuh dashboard (OpenSearch Dashboards saved objects).
Run on the Wazuh host. Takes the admin password from WAZUH_ADMIN_PASSWORD, otherwise from the installer's
wazuh-passwords.txt (WAZUH_PASSWORDS_FILE, default /root/wazuh-passwords.txt). Idempotent (overwrite=true)."""
import base64, json, os, re, ssl, urllib.request

pw = os.environ.get('WAZUH_ADMIN_PASSWORD') or re.search(
    r"indexer_username: 'admin'\s+indexer_password: '([^']+)'",
    open(os.environ.get('WAZUH_PASSWORDS_FILE', '/root/wazuh-passwords.txt')).read()).group(1)
AUTH = 'Basic ' + base64.b64encode(f'admin:{pw}'.encode()).decode()
CTX = ssl._create_unverified_context()
BASE = 'https://localhost'
IDX = 'wazuh-alerts-*'

def call(method, path, body=None):
    req = urllib.request.Request(BASE + path, method=method,
                                 data=json.dumps(body).encode() if body is not None else None,
                                 headers={'Authorization': AUTH, 'osd-xsrf': 'true', 'Content-Type': 'application/json'})
    return json.load(urllib.request.urlopen(req, context=CTX))

version = call('GET', '/api/status')['version']['number']

# The index pattern caches its field list; refresh it so data.mikrotik.* is known.
mf = '&'.join('meta_fields=' + f for f in ('_source', '_id', '_type', '_index', '_score'))
fields = call('GET', f'/api/index_patterns/_fields_for_wildcard?pattern={IDX}&{mf}')['fields']
call('PUT', f'/api/saved_objects/index-pattern/{IDX}', {'attributes': {'title': IDX, 'fields': json.dumps(fields)}})

def source(query):
    return {'searchSourceJSON': json.dumps({'query': {'query': query, 'language': 'kuery'}, 'filter': [],
                                            'indexRefName': 'kibanaSavedObjectMeta.searchSourceJSON.index'})}

REF = [{'name': 'kibanaSavedObjectMeta.searchSourceJSON.index', 'type': 'index-pattern', 'id': IDX}]
BASEQ = 'rule.groups:mikrotik'

def vis(vid, title, vis_state, query=BASEQ):
    vis_state['title'] = title
    call('POST', f'/api/saved_objects/visualization/{vid}?overwrite=true', {
        'attributes': {'title': title, 'visState': json.dumps(vis_state), 'uiStateJSON': '{}', 'description': '',
                       'version': 1, 'kibanaSavedObjectMeta': source(query)},
        'references': REF})
    return vid

COUNT = {'id': '1', 'enabled': True, 'type': 'count', 'schema': 'metric', 'params': {'customLabel': 'Events'}}
LAST = {'id': '9', 'enabled': True, 'type': 'max', 'schema': 'metric', 'params': {'field': 'timestamp', 'customLabel': 'Last'}}

def terms(aid, field, label, size=20, schema='bucket'):
    return {'id': aid, 'enabled': True, 'type': 'terms', 'schema': schema,
            'params': {'field': field, 'size': size, 'order': 'desc', 'orderBy': '1', 'otherBucket': False,
                       'missingBucket': False, 'customLabel': label}}

def table(vid, title, query, rows, per_page=10):
    return vis(vid, title, {'type': 'table', 'aggs': [COUNT, LAST] + rows,
                            'params': {'perPage': per_page, 'showPartialRows': False, 'showMetricsAtAllLevels': False,
                                       'sort': {'columnIndex': None, 'direction': None}, 'showTotal': False,
                                       'totalFunc': 'sum', 'percentageCol': ''}}, query)

def flt(q, label):
    return {'input': {'query': q, 'language': 'kuery'}, 'label': label}

panels = []

panels.append((vis('mikrotik-mikr-counts', 'MikroTik: last period', {
    'type': 'metric',
    'aggs': [{**COUNT, 'params': {'customLabel': 'count'}}, {'id': '2', 'enabled': True, 'type': 'filters', 'schema': 'group', 'params': {'filters': [
        flt('rule.groups:mikrotik', 'Alerts'),
        flt('rule.id:100202', 'Failed sign-ins'),
        flt('rule.id:100203', 'Password guessing'),
        flt('rule.id:100204', 'Config changes'),
        flt('rule.id:100205', 'Links down'),
        flt('rule.id:(100210 or 100212)', 'Errors'),
        flt('rule.level >= 7', 'Level 7 and up'),
    ]}}],
    'params': {'addTooltip': True, 'addLegend': False, 'type': 'metric', 'metric': {
        'percentageMode': False, 'useRanges': False, 'colorSchema': 'Green to Red', 'metricColorMode': 'None',
        'colorsRange': [{'from': 0, 'to': 10000}], 'labels': {'show': True}, 'invertColors': False,
        'style': {'bgFill': '#000', 'bgColor': False, 'labelColor': False, 'subText': '', 'fontSize': 32}}}}),
    dict(x=0, y=0, w=48, h=7)))

panels.append((vis('mikrotik-mikr-timeline', 'MikroTik: alerts over time, by device', {
    'type': 'histogram',
    'aggs': [COUNT,
             {'id': '2', 'enabled': True, 'type': 'date_histogram', 'schema': 'segment',
              'params': {'field': 'timestamp', 'useNormalizedOpenSearchInterval': True, 'interval': 'auto',
                         'drop_partials': False, 'min_doc_count': 1, 'extended_bounds': {}}},
             terms('3', 'predecoder.hostname', 'Device', 10, 'group')],
    'params': {'type': 'histogram', 'grid': {'categoryLines': False},
               'categoryAxes': [{'id': 'CategoryAxis-1', 'type': 'category', 'position': 'bottom', 'show': True,
                                 'style': {}, 'scale': {'type': 'linear'},
                                 'labels': {'show': True, 'filter': True, 'truncate': 100}, 'title': {}}],
               'valueAxes': [{'id': 'ValueAxis-1', 'name': 'LeftAxis-1', 'type': 'value', 'position': 'left',
                              'show': True, 'style': {}, 'scale': {'type': 'linear', 'mode': 'normal'},
                              'labels': {'show': True, 'rotate': 0, 'filter': False, 'truncate': 100},
                              'title': {'text': 'Alerts'}}],
               'seriesParams': [{'show': True, 'type': 'histogram', 'mode': 'stacked',
                                 'data': {'label': 'Events', 'id': '1'}, 'valueAxis': 'ValueAxis-1',
                                 'drawLinesBetweenPoints': True, 'lineWidth': 2, 'showCircles': True}],
               'addTooltip': True, 'addLegend': True, 'legendPosition': 'right', 'times': [],
               'addTimeMarker': False, 'labels': {'show': False},
               'thresholdLine': {'show': False, 'value': 10, 'width': 1, 'style': 'full', 'color': '#E7664C'}}}),
    dict(x=0, y=7, w=32, h=13)))

panels.append((table('mikrotik-mikr-by-device', 'MikroTik: alerts by device', BASEQ,
                     [terms('2', 'predecoder.hostname', 'Device')]),
               dict(x=32, y=7, w=16, h=13)))

panels.append((table('mikrotik-mikr-failed', 'MikroTik: failed sign-ins', 'rule.id:100202',
                     [terms('2', 'predecoder.hostname', 'Device'), terms('3', 'data.dstuser', 'User'),
                      terms('4', 'data.srcip', 'From'), terms('5', 'data.mikrotik.via', 'Via')]),
               dict(x=0, y=20, w=24, h=13)))

panels.append((table('mikrotik-mikr-changes', 'MikroTik: configuration changed by a person', 'rule.id:100204',
                     [terms('2', 'predecoder.hostname', 'Device'), terms('3', 'rule.description', 'Change', 50)]),
               dict(x=24, y=20, w=24, h=13)))

panels.append((table('mikrotik-mikr-links', 'MikroTik: links that went down', 'rule.id:100205',
                     [terms('2', 'predecoder.hostname', 'Device'), terms('3', 'rule.description', 'Link', 50)]),
               dict(x=0, y=33, w=16, h=13)))

panels.append((table('mikrotik-mikr-problems', 'MikroTik: errors and warnings',
                     'rule.id:(100207 or 100210 or 100211 or 100212 or 100213)',
                     [terms('2', 'predecoder.hostname', 'Device'), terms('3', 'rule.description', 'Message', 50)]),
               dict(x=16, y=33, w=32, h=13)))

call('POST', '/api/saved_objects/search/mikrotik-mikr-latest?overwrite=true', {
    'attributes': {'title': 'MikroTik: latest alerts', 'description': '',
                   'columns': ['predecoder.hostname', 'rule.level', 'rule.description'],
                   'sort': [['timestamp', 'desc']], 'version': 1, 'kibanaSavedObjectMeta': source(BASEQ)},
    'references': REF})

panel_json, refs = [], []
for i, (vid, grid) in enumerate(panels):
    panel_json.append({'version': version, 'gridData': {**grid, 'i': str(i + 1)}, 'panelIndex': str(i + 1),
                       'embeddableConfig': {}, 'panelRefName': f'panel_{i}'})
    refs.append({'name': f'panel_{i}', 'type': 'visualization', 'id': vid})
n = len(panels)
panel_json.append({'version': version, 'gridData': {'x': 0, 'y': 46, 'w': 48, 'h': 20, 'i': str(n + 1)},
                   'panelIndex': str(n + 1), 'embeddableConfig': {}, 'panelRefName': f'panel_{n}'})
refs.append({'name': f'panel_{n}', 'type': 'search', 'id': 'mikrotik-mikr-latest'})

call('POST', '/api/saved_objects/dashboard/mikrotik-mikr?overwrite=true', {
    'attributes': {'title': 'MikroTik (mikr)', 'description': 'Lines forwarded by mikr from MikroTik routers',
                   'panelsJSON': json.dumps(panel_json),
                   'optionsJSON': json.dumps({'hidePanelTitles': False, 'useMargins': True}),
                   'timeRestore': True, 'timeFrom': 'now-24h', 'timeTo': 'now',
                   'refreshInterval': {'pause': False, 'value': 60000}, 'version': 1,
                   'kibanaSavedObjectMeta': {'searchSourceJSON': json.dumps({'query': {'query': '', 'language': 'kuery'}, 'filter': []})}},
    'references': refs})

print('dashboard version', version, 'panels', n + 1, 'fields', len(fields))
