#!/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 sys # pylint: disable=C0413
import json # pylint: disable=C0413

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


__author__ = """Linuxfabrik GmbH, Zurich/Switzerland;
                originally written by Dominik Riva, Universitätsspital Basel/Switzerland"""
__version__ = '2025021501'

DESCRIPTION = """This check targets the JSON endpoint of https://www.cometsystem.com/ Web Sensors.
              """

DEFAULT_INSECURE = False
DEFAULT_NO_PROXY = False
DEFAULT_TIMEOUT = 5


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(
        '--severity',
        help='Severity for alerting, order matters, first match on part of a channel name wins. '
             'Have a look at the README for details. Example: '
             '`--severity temp:high:crit --severity dew:low:crit --severity humi:ok '
             '--severity warn`. '
             'Repeating. Default: warn',
        dest='SEVERITY',
        action='append',
        default=None,
    )

    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(
        '-u', '--url',
        help='Comet system URL pointing to the JSON file (http://example.com/values.json).',
        dest='URL',
        required=True,
    )

    return parser.parse_args()


def get_channel_state(channel, args):
    """Return the state for a given Web Sensor channel dict.
    Order of args.SEVERITY matters, first match wins.
    """
    if args.SEVERITY is not None:
        for severity in args.SEVERITY:
            sev = severity.split(':')
            if len(sev) == 1 and channel['alarm']:
                # there is no ":"
                return lib.base.str2state(sev[0])
            if len(sev) == 2:
                # "humi:warn"
                if sev[0].lower() in channel['name'].lower() and channel['alarm']:
                    return lib.base.str2state(sev[1])
            else:
                # "humi:low:warn", "temp:high:crit"
                # alarm == 1: high alarm
                if sev[0].lower() in channel['name'].lower() \
                and sev[1].lower().startswith('high') \
                and channel['alarm'] == 1:
                    return lib.base.str2state(sev[2])
                # alarm == 2: low alarm
                if sev[0].lower() in channel['name'].lower() \
                and sev[1].lower().startswith('low') \
                and channel['alarm'] == 2:
                    return lib.base.str2state(sev[2])
    if not channel['alarm']:
        return STATE_OK
    return STATE_WARN # the default state for any alarm, no matter if high or low


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

    # 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:
        if not args.URL.startswith('http'):
            lib.base.oao(
                '--url parameter has to start with "http://" or https://".',
                STATE_UNKNOWN,
            )
        # fetch the URL
        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)
    if 'ch1' not in result:
        lib.base.cu('Malformed Comet System Web Sensors status file.')

    # init some vars
    msg = ''
    state = STATE_OK
    perfdata = ''
    table_data = []

    # analyze data
    for key, value in result.items():
        if key.startswith('ch'):  # this is a channel
            if '%' in value['unit']:
                perfdata += lib.base.get_perfdata(value['name'], value['aval'], '%', _min=0, _max=100) # pylint: disable=C0301
            else:
                perfdata += lib.base.get_perfdata(value['name'], value['aval'])
            value['ch'] = key
            if value['unit'].endswith('C') or value['unit'].endswith('F'): # strip °
                value['unit'] = value['unit'][-1]
            channel_state = get_channel_state(value, args)
            state = lib.base.get_worst(channel_state, state)
            if value['alarm'] == 0:
                value['alarm'] = ''
            if value['alarm'] == 1:
                value['alarm'] = 'high'
            if value['alarm'] == 2:
                value['alarm'] = 'low'
            value['aval'] = '{}{}{}'.format(
                value['aval'],
                value['unit'],
                lib.base.state2str(channel_state, prefix=' '),
            )
            table_data.append(value)

    # build the message
    msg += ' on {} SN {}.\n\n'.format(
        result['devname'],
        result['devsn'],
    )
    if table_data:
        msg += lib.base.get_table(
            table_data,
            ['ch', 'name', 'alarm', 'aval'],
            ['Ch#', 'Name', 'Alarm', 'Value'],
        )

    if state == STATE_CRIT:
        msg = 'There are critical errors' + msg
    elif state == STATE_WARN:
        msg = 'There are warnings' + msg
    elif state == STATE_UNKNOWN:
        msg = 'Unknown states' + msg
    else:
        msg = 'Everything is ok' + msg

    # 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()
