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

import lib.args
import lib.base
import lib.lftest
import lib.shell
import lib.txt
from lib.globals import STATE_OK, STATE_UNKNOWN, STATE_WARN

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

DESCRIPTION = """Checks system clock and RTC settings via timedatectl, including whether network time
synchronization is active and whether the system clock is synchronized. Alerts when
the system clock is not synchronized to a time source, or when the RTC is kept in the
local time zone."""

DEFAULT_TIMEOUT = 8


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(
        '--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,
    )

    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:
        # Do NOT use `timedatectl show` if you want to be compatible to older
        # systemd versions, as this is only available since 2018-05 (and therefore not on RHEL 7).
        # Use `timedatectl status` instead.
        cmd = ['timedatectl', 'status']
        # timedatectl can wait for D-Bus for 25 seconds of its own
        success, result = lib.shell.shell_exec(cmd, timeout=args.TIMEOUT)
        if not success and result.startswith('Timeout after'):
            lib.base.oao(
                f'Timeout after {args.TIMEOUT}s while running `{" ".join(cmd)}`.',
                STATE_WARN,
                always_ok=args.ALWAYS_OK,
            )
        stdout, stderr, _retc = lib.base.coe((success, result))
        if stderr:
            lib.base.oao(f'{stderr}', STATE_WARN, always_ok=args.ALWAYS_OK)
    else:
        # do not call the command, put in test data
        stdout, stderr, _retc = lib.lftest.test(args.TEST)

    # init some vars
    msg = ''
    state = STATE_OK

    # analyze data
    rtc_in_local_tz = 'RTC in local TZ: yes' in stdout
    # systemd up to v238 says "NTP synchronized", later releases "System clock
    # synchronized". Both mean that the kernel considers the clock synchronized to a
    # time source, whichever service does the synchronizing.
    unsynchronized = any(
        f'{label}: no' in stdout
        for label in ('NTP synchronized', 'System clock synchronized')
    )

    # build the message
    # i->ntp_synced, yes_no(i->ntp), i->ntp_capable, in the wording of the systemd
    # release at hand
    for label in (
        'NTP synchronized',
        'System clock synchronized',
        'NTP enabled',
        'systemd-timesyncd.service active',
        'NTP service',
    ):
        msg += lib.txt.extract_str(
            stdout, f'{label}: ', '\n', include_fromto=True
        ).replace('\n', ', ')
    msg = msg[:-2]

    if rtc_in_local_tz:
        state = STATE_WARN
        msg += (
            '. The system is configured to read the RTC time in the local time zone. '
            'This mode cannot be fully supported. It will create various problems '
            'with time zone changes and daylight saving time adjustments. The RTC '
            'time is never updated, it relies on external facilities to maintain it. '
            'If at all possible, use RTC in UTC by calling '
            f'`timedatectl set-local-rtc 0`{lib.base.state2str(state, prefix=" ")}.'
        )
    else:
        msg += ', RTC in local TZ: no'

    if unsynchronized:
        state = STATE_WARN
        msg += ' ' if msg.endswith('.') else '. '
        msg += (
            'The system clock is not synchronized to a time source'
            f'{lib.base.state2str(STATE_WARN, prefix=" ")}. Check the time'
            ' synchronization service, for example with `chronyc sources` or'
            ' `timedatectl timesync-status`.'
        )

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


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