#!/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__ = '2026041201'

DESCRIPTION = """Checks the Windows Time Service (w32tm) status, including clock offset, stratum,
and time source. Useful for diagnosing time synchronization issues on Windows
servers.
Alerts when the clock offset exceeds the configured thresholds."""

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

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

    parser.add_argument(
        '--test',
        help=lib.args.help('--test'),
        dest='TEST',
        type=lib.args.csv,
    )

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

    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:
        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(local_state)}')
            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:
        lib.base.cu()
