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

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

DESCRIPTION = """Reports runtime facts of a Keycloak server via its HTTP API: uptime, the account
the service runs under, its Java runtime, and which Keycloak features are enabled and
disabled. Alerts if the server does not report this data to the account the check
authenticates with, which Keycloak grants only to an account holding the
"manage-realm" role in its administration realm. Tested with Keycloak 17 and later."""

DEFAULT_CLIENT_ID = 'admin-cli'
DEFAULT_INSECURE = False
DEFAULT_NO_PROXY = False
# Keycloak's install-time default password, not a real secret.
DEFAULT_PASSWORD = 'admin'  # nosec B105
DEFAULT_REALM = 'master'
DEFAULT_TIMEOUT = 8
DEFAULT_URL = 'http://127.0.0.1:8080'
DEFAULT_USERNAME = 'admin'


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(
        '--client-id',
        help='Keycloak API Client-ID. Default: %(default)s',
        dest='CLIENT_ID',
        default=DEFAULT_CLIENT_ID,
    )

    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(
        '-p',
        '--password',
        help='Keycloak API password. Default: %(default)s',
        dest='PASSWORD',
        default=DEFAULT_PASSWORD,
    )

    parser.add_argument(
        '--proxy',
        help=lib.args.help('--proxy'),
        dest='PROXY',
        default=None,
    )

    parser.add_argument(
        '--realm',
        help='Keycloak API realm. Default: %(default)s',
        dest='REALM',
        default=DEFAULT_REALM,
    )

    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='Keycloak API URL. Default: %(default)s',
        dest='URL',
        default=DEFAULT_URL,
    )

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

    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:
        # Discover the OIDC endpoints for the realm (no authentication needed),
        # obtain an admin access token and call the Admin REST API (fetch the
        # realm's details).
        oidc_config = lib.base.coe(lib.keycloak.discover_oidc_endpoints(args))
        admin_token = lib.base.coe(lib.keycloak.obtain_admin_token(args, oidc_config))
        server_info = lib.base.coe(
            lib.keycloak.get_data(args, admin_token, '/admin/serverinfo')
        )
    else:
        # do not call the API, put in test data
        server_info = lib.lftest.test_json(args.TEST)

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

    # analyze data
    system_info = lib.base.coe(
        lib.keycloak.get_server_info_section(server_info, 'systemInfo')
    )
    uptime = system_info.get('uptimeMillis', 0) / 1000

    features = server_info.get('features') or []
    enabled_features, disabled_features = [], []
    for feature in features:
        name = feature.get('name', 'n/a')
        feature_type = str(feature.get('type', 'n/a')).lower()
        if feature.get('enabled'):
            enabled_features.append(f'{name} ({feature_type})')
        else:
            disabled_features.append(f'{name} ({feature_type})')
    if not features:
        # Keycloak versions that do not list the features individually name the
        # disabled ones in the profile instead.
        profile_info = server_info.get('profileInfo') or {}
        disabled_features = profile_info.get('disabledFeatures') or []
    enabled_features.sort()
    disabled_features.sort()

    # build the message
    msg += f'Up {lib.human.seconds2human(uptime)}, '
    msg += f'running under user `{system_info.get("userName", "n/a")}`; '

    msg += f'Java v{system_info.get("javaVersion", "n/a")}, '
    msg += f'{system_info.get("javaVm", "n/a")}, '
    msg += f'{system_info.get("javaHome", "n/a")}\n'

    enabled_list = '\n* '.join(enabled_features)
    msg += '\nEnabled Features: '
    msg += f'\n* {enabled_list}\n' if enabled_features else 'None\n'

    disabled_list = '\n* '.join(disabled_features)
    msg += '\nDisabled Features: '
    msg += f'\n* {disabled_list}\n' if disabled_features else 'None\n'

    perfdata += lib.base.get_perfdata(
        'uptime',
        uptime,
        uom='s',
        warn=None,
        crit=None,
        _min=0,
        _max=None,
    )

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