#!/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.cache
import lib.human
import lib.lftest
import lib.net
import lib.time
import lib.txt
from lib.globals import STATE_OK, STATE_UNKNOWN

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

DESCRIPTION = """Monitors Java heap and non-heap memory usage of a Starface PBX via its monitoring
module on port 6556. Alerts when memory usage exceeds the configured thresholds.
Supports both IPv4 and IPv6. Data is cached to avoid overloading the PBX when multiple
checks run in parallel."""

DEFAULT_CACHE_EXPIRE = 1  # minutes
DEFAULT_CRIT = 90  # %
DEFAULT_HOSTNAME = 'localhost'
DEFAULT_IPV6 = False
DEFAULT_PORT = 6556
DEFAULT_TIMEOUT = 8  # seconds
DEFAULT_WARN = 80  # %


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(
        '--cache-expire',
        help=lib.args.help('--cache-expire') + ' Default: %(default)s',
        dest='CACHE_EXPIRE',
        type=int,
        default=DEFAULT_CACHE_EXPIRE,
    )

    parser.add_argument(
        '--critical',
        help=lib.args.help('--critical') + ' Default: >= %(default)s',
        dest='CRIT',
        default=DEFAULT_CRIT,
    )

    parser.add_argument(
        '-H',
        '--hostname',
        help='Starface PBX hostname or IP address. Default: %(default)s',
        dest='HOSTNAME',
        default=DEFAULT_HOSTNAME,
    )

    parser.add_argument(
        '--port',
        help='Starface PBX monitoring port. Default: %(default)s',
        dest='PORT',
        type=int,
        default=DEFAULT_PORT,
    )

    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(
        '--warning',
        help=lib.args.help('--warning') + ' Default: >= %(default)s',
        dest='WARN',
        default=DEFAULT_WARN,
    )

    parser.add_argument(
        '--ipv6',
        help=lib.args.help('--ipv6'),
        dest='USE_IPV6',
        action='store_true',
        default=DEFAULT_IPV6,
    )

    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)

    # fetch data
    if args.TEST is None:
        # fetch data from PBX, but first from cache
        data = lib.cache.get(
            args.HOSTNAME, filename='linuxfabrik-monitoring-plugins-starface.db'
        )
        if not data:
            data = lib.base.coe(
                lib.net.fetch(args.HOSTNAME, args.PORT, timeout=args.TIMEOUT)
            )
            lib.cache.set(
                args.HOSTNAME,
                data,
                lib.time.now() + args.CACHE_EXPIRE * 60,
                filename='linuxfabrik-monitoring-plugins-starface.db',
            )
    else:
        # do not call the command, put in test data
        stdout, _stderr, _retc = lib.lftest.test(args.TEST)
        data = stdout

    if not data:
        lib.base.cu(f'Got no valuable response from {args.HOSTNAME}:{args.PORT}.')

    # extract data
    result = lib.txt.extract_str(data, '<<<starface_java>>>', '<<<').splitlines()

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

    # build the message
    for row in result:
        if not row:
            continue

        if row.startswith('java_heap_memory'):
            mem_used, mem_max = row.replace('java_heap_memory ', '').split('/')
            mem_used = float(mem_used)
            mem_max = float(mem_max)
            if mem_max == -1:
                # unlimited memory usage allowed
                msg += f'Heap used: {lib.human.bytes2human(mem_used)} (unlimited memory usage allowed), '
                perfdata += lib.base.get_perfdata(
                    'heap-used',
                    mem_used,
                    uom='B',
                    _min=0,
                )
            else:
                used_percent = round(mem_used / mem_max * 100, 1)
                used_state = lib.base.get_state(
                    used_percent, args.WARN, args.CRIT, 'ge'
                )
                msg += (
                    f'Heap used: {used_percent}% '
                    f'({lib.human.bytes2human(mem_used)} '
                    f'of {lib.human.bytes2human(mem_max)})'
                    f'{lib.base.state2str(used_state, prefix=" ")}, '
                )
                state = lib.base.get_worst(state, used_state)
                perfdata += lib.base.get_perfdata(
                    'heap-used-percent',
                    used_percent,
                    uom='%',
                    warn=args.WARN,
                    crit=args.CRIT,
                    _min=0,
                    _max=100,
                )
                perfdata += lib.base.get_perfdata(
                    'heap-used',
                    mem_used,
                    uom='B',
                    _min=0,
                    _max=mem_max,
                )
                perfdata += lib.base.get_perfdata(
                    'heap-max',
                    mem_max,
                    uom='B',
                    _min=0,
                    _max=mem_max,
                )

        if row.startswith('java_non_heap_memory'):
            mem_used, mem_max = row.replace('java_non_heap_memory ', '').split('/')
            mem_used = float(mem_used)
            mem_max = float(mem_max)
            if mem_max == -1:
                # unlimited memory usage allowed
                msg += f'Non-Heap used: {lib.human.bytes2human(mem_used)} (unlimited memory usage allowed), '
                perfdata += lib.base.get_perfdata(
                    'non-heap-used',
                    mem_used,
                    uom='B',
                    _min=0,
                )
            else:
                used_percent = round(mem_used / mem_max * 100, 1)
                used_state = lib.base.get_state(
                    used_percent, args.WARN, args.CRIT, 'ge'
                )
                msg += (
                    f'Non-Heap used: {used_percent}% '
                    f'({lib.human.bytes2human(mem_used)} '
                    f'of {lib.human.bytes2human(mem_max)})'
                    f'{lib.base.state2str(used_state, prefix=" ")}, '
                )
                state = lib.base.get_worst(state, used_state)
                perfdata += lib.base.get_perfdata(
                    'non-heap-used-percent',
                    used_percent,
                    uom='%',
                    warn=args.WARN,
                    crit=args.CRIT,
                    _min=0,
                    _max=100,
                )
                perfdata += lib.base.get_perfdata(
                    'non-heap-used',
                    mem_used,
                    uom='B',
                    _min=0,
                    _max=mem_max,
                )
                perfdata += lib.base.get_perfdata(
                    'non-heap-max',
                    mem_max,
                    uom='B',
                    _min=0,
                    _max=mem_max,
                )

    # over and out
    lib.base.oao(msg[:-2], state, perfdata, always_ok=args.ALWAYS_OK)


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