#!/usr/bin/env python3
# -*- coding: utf-8; py-indent-offset: 4 -*-
#
# Author:  Linuxfabrik GmbH, Zurich, Switzerland
# Contact: info (at) linuxfabrik (dot) ch
#          https://www.linuxfabrik.ch/
# License: The Unlicense, see LICENSE file.

# https://github.com/Linuxfabrik/monitoring-plugins/blob/main/CONTRIBUTING.md

"""See the check's README for more details.
"""

import argparse  # pylint: disable=C0413
import json  # pylint: disable=C0413
import sys  # pylint: disable=C0413

import lib.args  # pylint: disable=C0413
import lib.base  # pylint: disable=C0413
import lib.shell  # pylint: disable=C0413
import lib.lftest  # pylint: disable=C0413
import lib.time  # pylint: disable=C0413
import lib.url  # pylint: disable=C0413
from lib.globals import (STATE_CRIT, STATE_OK,  # pylint: disable=C0413
                          STATE_UNKNOWN, STATE_WARN)

try:
    import flatdict  # pylint: disable=C0413
except ImportError as e:
    print('Python module "flatdict" is not installed.')
    sys.exit(STATE_UNKNOWN)


__author__ = 'Linuxfabrik GmbH, Zurich/Switzerland'
__version__ = '2025071801'

DESCRIPTION = """Statuspal is a status page provider from Germany. This check plugin gets
                 the summary of a Statuspal status page, checks its status, services,
                 active incidents and lists maintenances. You need to provide the
                 URL to the Statuspal API `summary` endpoint."""

DEFAULT_INSECURE = False
DEFAULT_NO_PROXY = False
DEFAULT_TIMEOUT  = 8
DEFAULT_URL = 'https://statuspal.eu/api/v2/status_pages/exoscalestatus/summary'


def parse_args():
    """Parse command line arguments using argparse.
    """
    parser = argparse.ArgumentParser(description=DESCRIPTION)

    parser.add_argument(
        '-V', '--version',
        action='version',
        version='%(prog)s: v{} by {}'.format(__version__, __author__)
    )

    parser.add_argument(
        '--always-ok',
        help='Always returns OK.',
        dest='ALWAYS_OK',
        action='store_true',
        default=False,
    )

    parser.add_argument(
        '--insecure',
        help='This option explicitly allows to perform "insecure" SSL connections. '
             'Default: %(default)s',
        dest='INSECURE',
        action='store_true',
        default=DEFAULT_INSECURE,
    )

    parser.add_argument(
        '--no-proxy',
        help='Do not use a proxy. '
             'Default: %(default)s',
        dest='NO_PROXY',
        action='store_true',
        default=DEFAULT_NO_PROXY,
    )

    parser.add_argument(
        '--test',
        help='For unit tests. Needs "path-to-stdout-file,path-to-stderr-file,expected-retc".',
        dest='TEST',
        type=lib.args.csv,
    )

    parser.add_argument(
        '--timeout',
        help='Network timeout in seconds. '
             'Default: %(default)s (seconds)',
        dest='TIMEOUT',
        type=int,
        default=DEFAULT_TIMEOUT,
    )

    parser.add_argument(
        '--url',
        help='Statuspal API URL. '
             'Default: %(default)s',
        dest='URL',
        default=DEFAULT_URL,
    )

    return parser.parse_args()


def concat_values(mydict, hierarchy):
    """Concat the values of the same hierarchy level in a dict.

    >>> mydict = {
        'services:0:name': 'Global',
        'services:0:description': 'Lorem ipsum',
        'services:0:children:0:name': 'DNS'
        'services:0:children:0:id': '4711'
        'services:0:children:0:children:1:name': 'Server01'
    }
    >>> concat_values(mydict, 'services:0:name')
    >>> 'Global'
    >>> concat_values(mydict, 'services:0:children:0:name')
    >>> 'Global.DNS'
    >>> concat_values(mydict, 'services:0:children:0:children:1:name')
    >>> 'Global.DNS.Server01'
    """
    result = ''
    hierarchy = hierarchy.split(':')
    for i, item in enumerate(hierarchy):  # pylint: disable=W0612
        # testing
        # * services:name
        # * services:0:name
        # * services:0:children:name
        # * services:0:children:0:name
        # etc.
        key = ':'.join(hierarchy[0:i+1]) + ':' + hierarchy[-1]
        if key in mydict:
            result += mydict[key] + '.'
    if result.endswith('.'):
        return result[:-1]
    return result


def statuspalstate2state(sps):
    """Convert Statuspal's incident level to the Nagios world.
    """
    if sps is None:
        return STATE_OK
    sps = sps.lower()
    if sps == 'scheduled' or sps == 'info':
        return STATE_OK
    if sps == 'minor' or sps == 'performance':
        return STATE_WARN
    if sps == 'major':
        return STATE_CRIT
    return STATE_UNKNOWN


def main():
    """The main function. Hier spielt die Musik.
    """

    # https://www.statuspal.io/api-docs/v2

    # parse the command line, exit with UNKNOWN if it fails
    try:
        args = parse_args()
    except SystemExit:
        sys.exit(STATE_UNKNOWN)

    # fetch data
    if args.TEST is None:
        result = lib.base.coe(lib.url.fetch_json(
            args.URL,
            insecure=args.INSECURE,
            no_proxy=args.NO_PROXY,
            timeout=args.TIMEOUT,
        ))
    else:
        # do not call the command, put in test data
        stdout, stderr, retc = lib.lftest.test(args.TEST)
        result = json.loads(stdout)

    try:
        result['status_page']['current_incident_type']
    except Exception as e:
        lib.base.cu('Unexpected response from {}'.format(args.URL))

    # init some vars
    msg = ''
    state = statuspalstate2state(result['status_page']['current_incident_type'])
    perfdata = ''
    table_data = []
    cnt_warn, cnt_crit = 0, 0

    # analyze data and build the message

    # status_page
    if state == STATE_OK:
        msg += 'All systems operational @ '
    if state == STATE_WARN:
        msg += 'Minor incidents @ '
    if state == STATE_CRIT:
        msg += 'Major incidents @ '
    if state == STATE_UNKNOWN:
        msg += 'Got state "{}" from {} for '.format(
            result['status_page']['current_incident_type'],
            args.URL,
        )
    msg += '{} ({}, TZ {})'.format(
        result['status_page']['name'],
        result['status_page']['url'],
        result['status_page']['time_zone'],
    )

    # incidents - get newest incident message only and print it on top
    if result['incidents']:
        msg += ': {}'.format(result['incidents'][0]['title'].strip())
        if result['incidents'][0]['updates']:
            msg += ' / {} ({})'.format(
                result['incidents'][0]['updates'][0]['description'].strip(),
                result['incidents'][0]['updates'][0]['updated_at'].replace('T', ' ').strip(),
            )
        msg += ' (see {})'.format(result['incidents'][0]['url']) if result['incidents'][0]['url'] else '' # pylint: disable=C0301

    # services - search for any incidents in services
    flattened_result = flatdict.FlatterDict(result['services'])
    item = {}
    for key, value in flattened_result.items():
        if key.endswith(':name'):
            item['name'] = concat_values(flattened_result, key)
        if key.endswith(':current_incident_type'):
            item['state'] = statuspalstate2state(value)
            if item['state'] == STATE_WARN:
                cnt_warn += 1
            if item['state'] == STATE_CRIT:
                cnt_crit += 1
            item['state'] = lib.base.state2str(item['state'], empty_ok=False)
        if len(item) == 2:
            table_data.append(item)
            item = {}
    if table_data:
        msg += '\n\n'
        msg += lib.base.get_table(
            table_data,
            ['name', 'state'],
            header=['Service', 'State'],
        )

    # maintenance
    table_data = []
    for maint in result['maintenances']:
        table_data.append({
            'type': maint['type'],
            'title': maint['title'],
            'starts_at': '{}'.format(
                lib.time.timestr2datetime(maint['starts_at'], pattern='%Y-%m-%dT%H:%M:%S'),
            ),
            # in case of an ongoing maint, ends_at is "None"
            'ends_at': '{}'.format(
                lib.time.timestr2datetime(maint['ends_at'], pattern='%Y-%m-%dT%H:%M:%S'),
            ) if maint.get('ends_at') is not None else 'in progress',
        })
    if table_data:
        msg += '\n'
        msg += lib.base.get_table(
            table_data,
            ['title', 'type', 'starts_at', 'ends_at'],
            header=['Maintenance', 'Type', 'Start', 'End'],
        )
        # get newest maintenance task
        if table_data[0]['starts_at'] <= lib.time.now(as_type='iso'):
            msg = 'Ongoing maintenance since {}: {}\n{}'.format(
                table_data[0]['starts_at'],
                table_data[0]['title'],
                msg,
            )

    # upcoming_maintenances (just fyi)
    table_data = []
    for maint in result['upcoming_maintenances']:
        table_data.append({
            'type': maint['type'],
            'title': maint['title'],
            'starts_at': '{}'.format(
                lib.time.timestr2datetime(maint['starts_at'], pattern='%Y-%m-%dT%H:%M:%S'),
            ),
            # in case of an ongoing maint, ends_at is "None"
            'ends_at': '{}'.format(
                lib.time.timestr2datetime(maint['ends_at'], pattern='%Y-%m-%dT%H:%M:%S'),
            ) if maint.get('ends_at') is not None else 'open end',
        })
    if table_data:
        msg += '\n'
        msg += lib.base.get_table(
            table_data,
            ['title', 'type', 'starts_at', 'ends_at'],
            header=['Upcoming Maintenance', 'Type', 'Start', 'End'],
        )
    perfdata += lib.base.get_perfdata(
        'cnt_warn',
        cnt_warn,
        uom=None,
        warn=None,
        crit=None,
        _min=0,
        _max=None,
    )
    perfdata += lib.base.get_perfdata(
        'cnt_crit',
        cnt_crit,
        uom=None,
        warn=None,
        crit=None,
        _min=0,
        _max=None,
    )

    # over and out
    lib.base.oao(msg, state, perfdata, always_ok=args.ALWAYS_OK)


if __name__ == '__main__':
    try:
        main()
    except Exception:   # pylint: disable=W0703
        lib.base.cu()
