#!/usr/lib64/linuxfabrik-monitoring-plugins/venv/bin/python
# -*- 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 re
import sys

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

try:
    import xmltodict
except ImportError:
    print('Python module "xmltodict" is not installed.')
    sys.exit(STATE_UNKNOWN)


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

DESCRIPTION = """Monitors CPU utilization on QNAP appliances running QTS via the API. Alerts only if
the threshold has been exceeded for a configurable number of consecutive check runs
(default: 5), suppressing short spikes."""

DEFAULT_COUNT = (
    5  # measurements; if check runs once per minute, this is a 5 minute interval
)
DEFAULT_CRIT = '90'  # %
DEFAULT_INSECURE = False
DEFAULT_NO_PROXY = False
DEFAULT_TIMEOUT = 6
DEFAULT_USERNAME = 'admin'
DEFAULT_WARN = '80'  # %


def parse_args():
    """Parse command line arguments using argparse."""
    parser = argparse.ArgumentParser(
        description=DESCRIPTION,
        epilog=lib.args.epilog(__file__),
        formatter_class=lib.args.HelpFormatter,
    )

    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(
        '--count',
        help=lib.args.help('--count') + ' Default: %(default)s',
        dest='COUNT',
        type=int,
        default=DEFAULT_COUNT,
    )

    parser.add_argument(
        '-c',
        '--critical',
        help=lib.args.help('--critical')
        + ' Supports Nagios ranges.'
        + ' Alerts only after `--count` consecutive runs above the threshold.'
        + ' Default: %(default)s',
        dest='CRIT',
        default=DEFAULT_CRIT,
    )

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

    parser.add_argument(
        '--no-perfdata',
        help=lib.args.help('--no-perfdata'),
        dest='NO_PERFDATA',
        action='store_true',
        default=False,
    )

    parser.add_argument(
        '--no-proxy',
        help=lib.args.help('--no-proxy'),
        dest='NO_PROXY',
        action='store_true',
        default=DEFAULT_NO_PROXY,
    )

    parser.add_argument(
        '--password',
        help='QTS API password.',
        dest='PASSWORD',
        required=True,
    )

    parser.add_argument(
        '--proxy',
        help=lib.args.help('--proxy'),
        dest='PROXY',
        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(
        '--url',
        help='QTS-based appliance URL. Example: `--url=https://192.0.2.10:8080`.',
        dest='URL',
        required=True,
    )

    parser.add_argument(
        '--username',
        help='QTS API username. Default: %(default)s',
        dest='USERNAME',
        default=DEFAULT_USERNAME,
    )

    parser.add_argument(
        '-w',
        '--warning',
        help=lib.args.help('--warning')
        + ' Supports Nagios ranges.'
        + ' Alerts only after `--count` consecutive runs above the threshold.'
        + ' Default: %(default)s',
        dest='WARN',
        default=DEFAULT_WARN,
    )

    args, _ = parser.parse_known_args()
    return args


def get_state_from_db(conn, table, count, warn, crit):
    """Return the state the last COUNT CPU usage samples agree on.

    A single sample above the threshold is a spike, COUNT of them in a row is load.
    Same hysteresis as `cpu-usage`.
    """
    # the table name comes from main(), every non-word character replaced
    sql = f'SELECT cpu_usage FROM {table}'  # nosec B608
    rows = lib.base.coe(lib.db_sqlite.select(conn, sql))
    if len(rows) < count:
        # still warming up, too few samples to tell a spike from a trend
        return STATE_OK
    states = [
        lib.base.get_state(row['cpu_usage'], warn, crit, _operator='range')
        for row in rows
    ]
    if all(item == STATE_CRIT for item in states):
        return STATE_CRIT
    if all(item in (STATE_CRIT, STATE_WARN) for item in states):
        return STATE_WARN
    return STATE_OK


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:
        auth_sid = lib.base.coe(lib.qts.get_auth_sid(args))
        url = (
            f'{args.URL}/cgi-bin/management/manaRequest.cgi'
            f'?subfunc=sysinfo&hd=no&multicpu=1&sid={auth_sid}'
        )
        result = lib.base.coe(
            lib.url.fetch(
                url,
                insecure=args.INSECURE,
                no_proxy=args.NO_PROXY,
                proxy=args.PROXY,
                timeout=args.TIMEOUT,
            )
        )
    else:
        # do not call the appliance, put in test data
        result, _, _ = lib.lftest.test(args.TEST)
    data = xmltodict.parse(result)['QDocRoot']
    if data['authPassed'] == '0':
        lib.base.cu('Insufficient permissions.')

    # init some vars
    stats = {}
    stats['cpu_usage'] = float(
        data['func']['ownContent']['root']['cpu_usage'].replace('%', '')
    )

    # analyze data
    # In test mode (--test), keep the history in memory, so that unit tests neither
    # depend on nor leave behind a state file.
    definition = """
        cpu_usage REAL NOT NULL
    """
    conn = lib.base.coe(
        lib.db_sqlite.connect(
            filename='linuxfabrik-monitoring-plugins-qts-cpu-usage.db',
            in_memory=args.TEST is not None,
        )
    )
    # One monitoring host usually checks several appliances. Each gets a table of its
    # own, named after its URL (as in fortios-network-io), so the load of one appliance
    # never counts towards the consecutive runs of another.
    table = 'perfdata_' + re.sub(r'\W+', '_', args.URL)
    lib.base.coe(lib.db_sqlite.create_table(conn, definition, table=table))

    # save trend data to local sqlite database, limited to "count" rows max.
    lib.base.coe(lib.db_sqlite.insert(conn, stats, table=table))
    lib.base.coe(lib.db_sqlite.cut(conn, table=table, _max=args.COUNT))
    lib.base.coe(lib.db_sqlite.commit(conn))

    state = get_state_from_db(conn, table, args.COUNT, args.WARN, args.CRIT)
    lib.db_sqlite.close(conn)

    # build the message
    msg = f'{stats["cpu_usage"]}%{lib.base.state2str(state, prefix=" ")}'
    perfdata = lib.base.get_perfdata(
        'cpu-usage',
        stats['cpu_usage'],
        uom='%',
        warn=args.WARN,
        crit=args.CRIT,
        _min=0,
        _max=100,
    )

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


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