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

"""Have a look at the check's README for further details
"""

import argparse  # pylint: disable=C0413
import base64  # pylint: disable=C0413
import sys  # pylint: disable=C0413

import lib.base  # pylint: disable=C0413
import lib.txt  # pylint: disable=C0413
import lib.url  # pylint: disable=C0413
import lxml.etree as ET  # pylint: disable=C0413
from lib.globals import (STATE_OK, STATE_UNKNOWN,  # pylint: disable=C0413
                          STATE_WARN)

__author__ = """Linuxfabrik GmbH, Zurich/Switzerland;
                originally written by Simon Wunderlin,
                adapted by Dominik Riva, Universitätsspital Basel/Switzerland"""
__version__ = '2024031401'


DESCRIPTION = """This plugin checks for a matching string in a XML document, fetched via http(s).
                 Simple XPath syntax, prefix namespaces and HTTP Basic Auth are supported.
              """

DEFAULT_INSECURE = False
DEFAULT_NO_PROXY = False
DEFAULT_TIMEOUT = 7


def parse_args():
    """Parse command line agruments using argparse.
    """
    parser = argparse.ArgumentParser(description=DESCRIPTION)

    parser.add_argument(
        '-V', '--version',
        action='version',
        version='{0}: v{1} by v{2}'.format('%(prog)s', __version__, __author__)
    )

    parser.add_argument(
        '--always-ok',
        help='Always return OK.',
        dest='ALWAYS_OK',
        action='store_true',
        default=False,
    )

    parser.add_argument(
        '--expect',
        help='String to expect in the xpath\'s location. If ommitted, just checks '
             'if the XPath exists.',
        dest='EXPECT',
    )

    parser.add_argument(
        '--insecure',
        help='This option explicitly allows to perform "insecure" SSL connections. '
             'Default: %(default)s',
        dest='INSECURE',
        action='store_true',
        default=DEFAULT_INSECURE,
    )

    parser.add_argument(
        '--namespace',
        help='If your XPath expression uses namespace prefixes, you must define them in a prefix '
             'mapping. This parameter expects a mapping for the namespace prefix used in the '
             'XPath expression to namespace URI (repeatable). For example like so: '
             '--namespace="prefix1:https://schemas.xmlsoap.org/prefix1/" '
             '--namespace="prefix2:https://schemas.xmlsoap.org/prefix2/"',
        dest='NAMESPACES',
        action='append',
    )

    parser.add_argument(
        '--no-proxy',
        help='Do not use a proxy. '
             'Default: %(default)s',
        dest='NO_PROXY',
        action='store_true',
        default=DEFAULT_NO_PROXY,
    )

    parser.add_argument(
        '--password',
        help='Password (HTTP Basic Auth).',
        dest='PASSWORD',
    )

    parser.add_argument(
        '--timeout',
        help='Network timeout in seconds. '
             'Default: %(default)s (seconds)',
        dest='TIMEOUT',
        type=int,
        default=DEFAULT_TIMEOUT,
    )

    parser.add_argument(
        '--url',
        help='WSDL Endpoint URL. '
             'Default: %(default)s',
        dest='URL',
        required=True,
    )

    parser.add_argument(
        '--username',
        help='Username (HTTP Basic Auth).',
        dest='USERNAME',
    )

    parser.add_argument(
        '--xpath',
        help='XPath to query. The result must point to a single value '
             '(attribute or node content). Lists/arrays are not supported.',
        dest='XPATH',
        required=True,
    )

    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)

    header = {
        'Accept': 'application/xml',
        'Connection': 'keep-alive',
        'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) '
                      'Chrome/100.0.4896.127 Safari/537.36',
    }

    # Authorization (if needed)
    if args.USERNAME and args.PASSWORD:
        auth = '{}:{}'.format(args.USERNAME, args.PASSWORD)
        encoded_auth = lib.txt.to_text(base64.b64encode(lib.txt.to_bytes(auth)))
        header['Authorization'] = 'Basic {}'.format(encoded_auth)

    # fetch data
    result = lib.base.coe(lib.url.fetch(
        args.URL,
        header=header,
        insecure=args.INSECURE,
        no_proxy=args.NO_PROXY,
        timeout=args.TIMEOUT,
    ))

    # analyze data
    try:
        doc = ET.fromstring(lib.txt.to_bytes(result))
    except Exception:
        lib.base.cu('Failed to parse XML.')

    # https://lxml.de/xpathxslt.html
    if args.NAMESPACES:
        namespaces = {}
        for ns in args.NAMESPACES:
            try:
                namespaces[ns.split(':', 1)[0]] = ns.split(':', 1)[1]
            except:
                lib.base.cu('Wrong namespace mapping syntax for "{}".'.format(ns))
        try:
            r = doc.xpath(args.XPATH, namespaces=namespaces)
        except Exception:
            lib.base.cu('An XML xpath error occurred.')
    else:
        try:
            r = doc.xpath(args.XPATH)
        except Exception:
            lib.base.cu('An XML xpath error occurred.')

    # over and out
    if args.EXPECT is None:
        # just prove that there is a valid answer
        if r:
            lib.base.oao('Everything is ok.', STATE_OK)
        else:
            state = STATE_WARN
            lib.base.oao(
                'Empty result{}.'.format(lib.base.state2str(state, prefix=' ')),
                state,
                always_ok=args.ALWAYS_OK
            )
    else:
        # search for a string
        try:
            result = r[0].text
        except:
            lib.base.cu('Response types other than string are not supported.')
        if args.EXPECT in r[0].text:
            lib.base.oao(
                'Everything is ok, "{}" found in result "{}".'.format(
                    args.EXPECT,
                    r[0].text,
                ),
                STATE_OK,
            )
        else:
            state = STATE_WARN
            lib.base.oao(
                '"{}" not found in "{}"{}.'.format(
                    args.EXPECT,
                    r[0].text,
                    lib.base.state2str(state, prefix=' ')
                ),
                state,
                always_ok=args.ALWAYS_OK,
            )


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