#!/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.lftest
import lib.shell
import lib.txt
from lib.globals import (STATE_OK, STATE_UNKNOWN, STATE_WARN)

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

DESCRIPTION = '''This monitoring plugin runs `w32tm /query /status /verbose` (Windows)
                 to help diagnose problems with the time settings.'''

DEFAULT_CRIT = 129600 # s (36h)
DEFAULT_STRATUM = 6
DEFAULT_WARN = 28800  # s (8h)


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(
        '-c', '--critical',
        help='Set the critical threshold for the time since "Last Good Sync", in s. '
             'Default: %(default)ss',
        dest='CRIT',
        type=int,
        default=DEFAULT_CRIT,
    )

    parser.add_argument(
        '--stratum',
        help=lib.args.help('--stratum') + ' '
             + 'Default: %(default)s',
        dest='STRATUM',
        default=DEFAULT_STRATUM,
    )

    parser.add_argument(
        '--test',
        help='For unit tests. Needs "path-to-stdout-file,path-to-stderr-file,expected-retc".',
        dest='TEST',
        type=lib.args.csv,
    )

    parser.add_argument(
        '-w', '--warning',
        help='Set the warning threshold for the time since "Last Good Sync", in s. '
             'Default: %(default)ss',
        dest='WARN',
        type=int,
        default=DEFAULT_WARN,
    )

    return parser.parse_args()


def main():
    """The main function. Hier spielt die Musik.
    """

    # parse the command line, exit with UNKNOWN if it fails
    try:
        args = parse_args()
    except SystemExit:
        sys.exit(STATE_UNKNOWN)

    if args.TEST is None:
        cmd = 'w32tm /query /status /verbose'
        stdout, _, retc = lib.base.coe(lib.shell.shell_exec(cmd))
    else:
        # do not call the command, put in test data
        stdout, _, retc = lib.lftest.test(args.TEST)
    if retc != 0:
        lib.base.cu(stdout)  # Windows prints the error to stdout

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

    clock_rate = 0
    leap_indicator = 0
    peer_used = False
    phase_offset = 0
    precision = 0
    root_delay = 0
    root_dispersion = 0
    stratum = 0
    time_since_last_good_sync_time = 0

    # analyze data
    for line in stdout.splitlines():
        line = line.strip()
        if line.startswith('Stratum'):
            stratum = int(line.split()[1])
            peer_used = stratum != 0
            if not peer_used:
                msg.append(f'No NTP server used {lib.base.state2str(STATE_WARN)}')
                state = STATE_WARN
            if stratum >= args.STRATUM:
                msg.append(f'{line} {lib.base.state2str(STATE_WARN)}')
                state = STATE_WARN
            continue
        if line.startswith('Leap Indicator'):
            leap_indicator = lib.txt.extract_str(line, ': ', '(')
            if line != 'Leap Indicator: 0(no warning)':
                msg.append(line.replace('(', ' ('))
                state = STATE_WARN
        if line.startswith('Precision'):
            precision = lib.txt.extract_str(line, ': ', '(').strip()
        if line.startswith('Root Delay'):
            root_delay = float(line.split()[-1].replace('s', '')) * 1000  # convert to ms
            continue
        if line.startswith('Root Dispersion'):
            root_dispersion = float(line.split()[-1].replace('s', '')) * 1000  # convert to ms
            continue
        if line.startswith('Phase Offset'):
            phase_offset = float(line.split()[-1].replace('s', '')) * 1000  # convert to ms
            continue
        if line.startswith('ClockRate'):
            clock_rate = float(line.split()[-1].replace('s', '')) * 1000  # convert to ms
            continue
        if line.startswith('Last Sync Error'):
            if 'Last Sync Error: 0' not in line:
                msg.append(line)
                state = STATE_WARN
            continue
        if line.startswith('Time since Last Good Sync Time'):
            time_since_last_good_sync_time = float(line.split()[-1].replace('s', ''))
            local_state = lib.base.get_state(time_since_last_good_sync_time, args.WARN, args.CRIT)
            if local_state != STATE_OK:
                msg.append(f'{line} {lib.base.state2str(STATE_WARN)}')
            state = lib.base.get_worst(local_state, state)

    # build the message
    if state == STATE_OK:
        msg.append('Everything is ok.')

    perfdata += lib.base.get_perfdata(
        'clock_rate',
        clock_rate,
        uom='ms',
        warn=None,
        crit=None,
        _min=None,
        _max=None,
    )
    perfdata += lib.base.get_perfdata(
        'leap_indicator',
        leap_indicator,
        uom=None,
        warn=None,
        crit=None,
        _min=0,
        _max=None,
    )
    perfdata += lib.base.get_perfdata(
        'phase_offset',
        phase_offset,
        uom='ms',
        warn=None,
        crit=None,
        _min=None,
        _max=None,
    )
    perfdata += lib.base.get_perfdata(
        'precision',
        precision,
        uom=None,
        warn=None,
        crit=None,
        _min=None,
        _max=None,
    )
    perfdata += lib.base.get_perfdata(
        'root_delay',
        root_delay,
        uom='ms',
        warn=None,
        crit=None,
        _min=None,
        _max=None,
    )
    perfdata += lib.base.get_perfdata(
        'root_dispersion',
        root_dispersion,
        uom='ms',
        warn=None,
        crit=None,
        _min=None,
        _max=None,
    )
    perfdata += lib.base.get_perfdata(
        'stratum',
        stratum,
        uom=None,
        warn=args.STRATUM,
        crit=None,
        _min=0,
        _max=16,
    )
    perfdata += lib.base.get_perfdata(
        'time_since_last_good_sync_time',
        time_since_last_good_sync_time,
        uom='s',
        warn=None,
        crit=None,
        _min=None,
        _max=None,
    )


    # over and out
    lib.base.oao(', '.join(msg) + '\n\n' + stdout, state, perfdata)


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