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

import lib.args
import lib.base
import lib.db_mysql
import lib.human
import lib.librenms
import lib.txt
from lib.globals import STATE_OK, STATE_UNKNOWN

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

DESCRIPTION = """Checks for unacknowledged alerts in LibreNMS and reports the most recent alert per
device. Only considers devices that do not have alerting disabled in their LibreNMS
settings. Requires direct access to the LibreNMS MySQL/MariaDB database.
Supports extended reporting via --lengthy."""

DEFAULT_DEFAULTS_FILE = '/var/spool/icinga2/.my.cnf'
DEFAULT_DEFAULTS_GROUP = 'client'
DEFAULT_LENGTHY = False
DEFAULT_SERVERITY = 'crit'
DEFAULT_TIMEOUT = 3


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(
        '--defaults-file',
        help=lib.args.help('--defaults-file') + ' '
        'Example: `/var/spool/icinga2/.my.cnf`. '
        'Default: %(default)s',
        dest='DEFAULTS_FILE',
        default=DEFAULT_DEFAULTS_FILE,
    )

    parser.add_argument(
        '--defaults-group',
        help=lib.args.help('--defaults-group') + ' Default: %(default)s',
        dest='DEFAULTS_GROUP',
        default=DEFAULT_DEFAULTS_GROUP,
    )

    parser.add_argument(
        '--device-group',
        help='Filter by LibreNMS device group. Supports SQL wildcards.',
        dest='DEVICE_GROUP',
    )

    parser.add_argument(
        '--device-hostname',
        help='Filter by LibreNMS hostname. Can be specified multiple times.',
        dest='DEVICE_HOSTNAME',
        action='append',
    )

    parser.add_argument(
        '--device-type',
        help='Filter by LibreNMS device type. Can be specified multiple times.',
        dest='DEVICE_TYPE',
        action='append',
        choices=[  # taken from the librenms source file resources/definitions/config_definitions.json
            'appliance',
            'collaboration',
            'environment',
            'firewall',
            'loadbalancer',
            'management',
            'network',
            'power',
            'printer',
            'server',
            'storage',
            'wireless',
            'workstation',
        ],
    )

    parser.add_argument(
        '--lengthy',
        help=lib.args.help('--lengthy'),
        dest='LENGTHY',
        action='store_true',
        default=DEFAULT_LENGTHY,
    )

    parser.add_argument(
        '--severity',
        help=lib.args.help('--severity') + ' Default: %(default)s',
        dest='SEVERITY',
        default=DEFAULT_SERVERITY,
        choices=['warn', 'crit'],
    )

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

    args, _ = parser.parse_known_args()
    return args


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)

    mysql_connection = {
        'defaults_file': args.DEFAULTS_FILE,
        'defaults_group': args.DEFAULTS_GROUP,
        'timeout': args.TIMEOUT,
    }
    conn = lib.base.coe(lib.db_mysql.connect(mysql_connection))
    lib.base.coe(lib.db_mysql.check_select_privileges(conn))

    # init some vars
    msg = ''
    perfdata = ''
    state = STATE_OK
    alert_count = 0

    # fetch data
    sql = """
        SELECT
            d.hardware,
            d.hostname,
            d.os,
            d.sysName,
            d.sysDescr,
            d.type,
            d.uptime,
            l.location,
            a.state,
            ar.name AS ar_name
        FROM
            devices AS d
                LEFT JOIN
            locations AS l ON d.location_id = l.id
                LEFT JOIN
            device_group_device AS dgd ON d.device_id = dgd.device_id
                LEFT JOIN
            device_groups AS dg ON dgd.device_group_id = dg.id
                LEFT JOIN
            alerts AS a ON d.device_id = a.device_id
                AND a.open = 1
                AND a.state IN (1, 3, 4, 5)
                LEFT JOIN
            alert_rules AS ar ON ar.id = a.rule_id
        WHERE
            disable_notify = 0
    """
    data = []
    if args.DEVICE_GROUP:
        sql += ' AND dg.name LIKE %s '
        data.append(args.DEVICE_GROUP)
    if args.DEVICE_HOSTNAME:
        sql += f' AND d.hostname IN ({", ".join("%s" for _ in args.DEVICE_HOSTNAME)}) '
        data += args.DEVICE_HOSTNAME
    if args.DEVICE_TYPE:
        sql += f' AND d.type IN ({", ".join("%s" for _ in args.DEVICE_TYPE)}) '
        data += args.DEVICE_TYPE
    devices = lib.base.coe(lib.db_mysql.select(conn, sql, data))
    lib.db_mysql.close(conn)
    device_count = len(devices)

    # enrich and analyse data
    for i, device in enumerate(devices):
        if device['uptime']:
            devices[i]['uptime'] = lib.human.seconds2human(device['uptime'])
        if not device['sysName']:
            devices[i]['sysName'] = device['sysDescr']
        local_state = lib.librenms.get_state(device['state'], args.SEVERITY)
        if local_state != STATE_OK:
            alert_count += 1
        devices[i]['state'] = lib.base.state2str(
            local_state,
            empty_ok=False,
        )
        state = lib.base.get_worst(local_state, state)

    # filter data if compact layout is choosen (just get everything that is not ok)
    if not args.LENGTHY:
        # brief data
        devices = [device for device in devices if device['state'] != '[OK]']

    # build the message
    if state == STATE_OK:
        msg = 'Everything is ok. '
    else:
        msg = (
            f'There {lib.txt.pluralize("", alert_count, "is,are")} '
            f'{alert_count} {lib.txt.pluralize("alert", alert_count)}. '
        )
    msg += f'Checked {device_count} {lib.txt.pluralize("device", device_count)}.'
    msg += '\n\n'

    perfdata += lib.base.get_perfdata(
        'device_count',
        device_count,
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'alert_count',
        alert_count,
        _min=0,
    )

    if device_count > 0:
        if not args.LENGTHY:
            msg += lib.base.get_table(
                devices,
                [
                    'hostname',
                    'sysName',
                    'ar_name',
                    'state',
                ],
                header=[
                    'Hostname',
                    'SysName',
                    'Alert',
                    'State',
                ],
            )
        else:
            msg += lib.base.get_table(
                devices,
                [
                    'hostname',
                    'sysName',
                    'hardware',
                    'type',
                    'os',
                    'location',
                    'uptime',
                    'ar_name',
                    'state',
                ],
                header=[
                    'Hostname',
                    'SysName',
                    'Hardware',
                    'Type',
                    'OS',
                    'Location',
                    'Uptime',
                    'Alert',
                    'State',
                ],
            )

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


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