#!/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
import json
import sys

import lib.args
import lib.base
import lib.lftest
import lib.url
from lib.globals import STATE_CRIT, STATE_OK, STATE_UNKNOWN, STATE_WARN

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

DESCRIPTION = """Reads sensor data from Comet System Web Sensors via their JSON API endpoint. Monitors
channels such as temperature, humidity, and other environmental values. Alarm states
are mapped to configurable severity levels using a flexible pattern matching system
(e.g. "temp:high:crit", "humi:low:warn")."""

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=f'%(prog)s: v{__version__} by {__author__}',
    )

    parser.add_argument(
        '--always-ok',
        help=lib.args.help('--always-ok'),
        dest='ALWAYS_OK',
        action='store_true',
        default=False,
    )

    parser.add_argument(
        '--insecure',
        help=lib.args.help('--insecure'),
        dest='INSECURE',
        action='store_true',
        default=DEFAULT_INSECURE,
    )

    parser.add_argument(
        '--no-proxy',
        help=lib.args.help('--no-proxy'),
        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. '
        'Can be specified multiple times. '
        'Example: '
        '`--severity temp:high:crit --severity dew:low:crit --severity humi:ok '
        '--severity warn`. '
        'Default: warn.',
        dest='SEVERITY',
        action='append',
        default=None,
    )

    parser.add_argument(
        '--test',
        help=lib.args.help('--test'),
        dest='TEST',
        type=lib.args.csv,
    )

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

    parser.add_argument(
        '-u',
        '--url',
        help='Comet System URL pointing to the JSON endpoint. '
        'Example: `http://example.com/values.json`.',
        dest='URL',
        required=True,
    )

    args, _ = parser.parse_known_args()
    return 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. This is where the magic happens."""

    # parse the command line
    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
                )
            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'] = (
                f'{value["aval"]}'
                f'{value["unit"]}'
                f'{lib.base.state2str(channel_state, prefix=" ")}'
            )
            table_data.append(value)

    # build the message
    msg += f' on {result["devname"]} SN {result["devsn"]}.\n\n'
    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:
        lib.base.cu()
