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

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

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

DESCRIPTION = """Reports the state of network bonding (channel bonding) interfaces. Checks that all
slave interfaces are active and that the bonding mode and link status are healthy.
Channel bonding allows two or more network interfaces to act as one, increasing
bandwidth and providing redundancy.
Alerts when any slave interface is down or the bond is degraded.
Requires root or sudo."""


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(
        '--test',
        help='For unit tests. Needs "path-to-bonding-file". '
        'Example: `--test /tmp/bond0`.',
        dest='TEST',
        type=str,
    )

    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:
        try:
            bonding_interfaces = os.listdir('/proc/net/bonding/')
        except OSError:
            bonding_interfaces = []
    else:
        bonding_interfaces = ['bond0']

    if len(bonding_interfaces) == 0:
        lib.base.cu('No bonding interfaces found.')


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

    for bonding_interface in bonding_interfaces:
        if args.TEST is None:
            filename = f'/proc/net/bonding/{bonding_interface}'
        else:
            filename = args.TEST

        with open(filename, 'r', encoding='utf-8') as result:
            submsg = ''
            substate = STATE_OK
            # now, check the media-independent interface status (MII)
            bonding_mode = ''
            in_interface_section = False
            link_failure_count = '0'
            mii_status = 'up'
            slave_interface = ''
            for row in result:
                row = row.strip()
                if not row:
                    # empty line after the end of an interface section
                    if in_interface_section:
                        perfdata += lib.base.get_perfdata(
                            f'{bonding_interface}_{slave_interface}_link_failure_count',
                            link_failure_count,
                            warn=1,
                            _min=0,
                        )
                        if mii_status != 'up':
                            submsg += (
                                f'    * {slave_interface}'
                                f' is {mii_status}'
                                f' with {link_failure_count}'
                                f' link'
                                f' {lib.txt.pluralize("failure", link_failure_count)}'
                                f'.\n'
                            )
                            substate = state = STATE_WARN
                        in_interface_section = False
                    continue
                if ': ' not in row:
                    continue
                key, value = row.split(': ')
                if key == 'Bonding Mode':
                    bonding_mode = value
                if key == 'Link Failure Count':
                    link_failure_count = value
                if key == 'MII Status':
                    mii_status = value
                if key == 'Slave Interface':
                    slave_interface = value
                    in_interface_section = True
                if (
                    key == 'Partner Mac Address'
                    and value == '00:00:00:00:00:00'
                    and bonding_mode == 'IEEE 802.3ad Dynamic link aggregation'
                ):
                    submsg += (
                        '    * Could not detect the MAC Address of the switch.'
                        ' This could indicate that LACP is not configured'
                        ' properly.\n'
                    )
                    substate = state = STATE_WARN

            if in_interface_section:
                perfdata += lib.base.get_perfdata(
                    f'{bonding_interface}_{slave_interface}_link_failure_count',
                    link_failure_count,
                    warn=1,
                    _min=0,
                )
                if mii_status != 'up':
                    submsg += (
                        f'    * {slave_interface}'
                        f' is {mii_status}'
                        f' with {link_failure_count}'
                        f' link'
                        f' {lib.txt.pluralize("failure", link_failure_count)}'
                        f'.\n'
                    )
                    substate = state = STATE_WARN


            # build the message
            msg += (
                f'* {lib.base.state2str(substate, suffix=" ")}'
                f'{bonding_interface} ({bonding_mode})\n' + submsg
            )

    if state == STATE_OK:
        msg = 'Everything is ok.\n\n' + msg
    else:
        msg = 'One or more errors.\n\n' + msg

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


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