#!/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  # pylint: disable=C0413
import sys  # pylint: disable=C0413

import lib.base  # pylint: disable=C0413
import lib.cache  # pylint: disable=C0413
import lib.human  # pylint: disable=C0413
import lib.time  # pylint: disable=C0413
import lib.txt  # pylint: disable=C0413
import lib.url  # pylint: disable=C0413
from lib.globals import (STATE_CRIT, STATE_OK,  # pylint: disable=C0413
                          STATE_UNKNOWN, STATE_WARN)

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

DESCRIPTION = """This check gets some recent activity from Metabase."""

DEFAULT_CACHE_EXPIRE = 13*24 + 23 # hours; in Metabase, by default, sessions are good for 14 days
DEFAULT_CRIT = 90
DEFAULT_INSECURE = False
DEFAULT_NO_PROXY = False
DEFAULT_TIMEOUT = 8
DEFAULT_URL = 'http://localhost:3000'
DEFAULT_USERNAME = 'metabase-admin'
DEFAULT_WARN = 80


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

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

    parser.add_argument(
        '--cache-expire',
        help='The amount of time after which the credential cache expires, in hours. '
             'Default: %(default)s',
        dest='CACHE_EXPIRE',
        type=int,
        default=DEFAULT_CACHE_EXPIRE,
    )

    parser.add_argument(
        '-c', '--critical',
        help='Set the CRIT threshold as a percentage. '
             'Default: >= %(default)s',
        dest='CRIT',
        type=int,
        default=DEFAULT_CRIT,
    )

    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(
        '--no-proxy',
        help='Do not use a proxy. '
             'Default: %(default)s',
        dest='NO_PROXY',
        action='store_true',
        default=DEFAULT_NO_PROXY,
    )

    parser.add_argument(
        '-p', '--password',
        help='Metabase API password.',
        dest='PASSWORD',
        required=True,
    )

    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='Metabase API URL. '
             'Default: %(default)s',
        dest='URL',
        default=DEFAULT_URL,
    )

    parser.add_argument(
        '--username',
        help='Metabase API username. '
             'Default: %(default)s',
        dest='USERNAME',
        default=DEFAULT_USERNAME,
    )

    parser.add_argument(
        '-w', '--warning',
        help='Set the WARN threshold as a percentage. '
             'Default: >= %(default)s',
        dest='WARN',
        type=int,
        default=DEFAULT_WARN,
    )

    return parser.parse_args()


def get_token(metabase_url, user, password, expire):
    """Gets an API token from Metabase, using your credentials.
    Equivalent to:

    $ curl -X POST \
    $     -H "Content-Type: application/json" \
    $     -d '{"username": "person@metabase.com", "password": "fakepassword"}' \
    $     http://localhost:3000/api/session
    """
    # we cache credentials to reuse them until they expire, because logins are rate-limited for security
    token = lib.cache.get('metabase-token', filename='linuxfabrik-monitoring-plugins-metabase-stats.db')
    if token:
        return (True, token)

    metabase_url += '/api/session'
    header = {
        'Content-Type': 'application/json',
        }
    data = {
        'username': user,
        'password': password,
        }
    success, result = lib.url.fetch_json(
        metabase_url,
        header=header,
        data=data,
        encoding='serialized-json',
        insecure=args.INSECURE,
        no_proxy=args.NO_PROXY,
        timeout=args.TIMEOUT,
    )
    if not success:
        return (success, result)
    if not result:
        return (False, 'There was no result from {}.'.format(metabase_url))
    if 'id' not in result:
        return (False, 'Something went wrong, maybe wrong username/password.')
    token = result['id']
    lib.cache.set('metabase-token', token, lib.time.now() + expire, filename='linuxfabrik-monitoring-plugins-metabase-stats.db')
    return (True, token)


def get_data(metabase_url, token, data={}):
    header = {
        'Content-Type': 'application/json',
        'X-Metabase-Session': token,
        }
    return lib.url.fetch(
        metabase_url,
        header=header,
        data=data,
        encoding='serialized-json',
        insecure=args.INSECURE,
        no_proxy=args.NO_PROXY,
        timeout=args.TIMEOUT,
    )


def get_json(metabase_url, token, data={}):
    header = {
        'Content-Type': 'application/json',
        'X-Metabase-Session': token,
        }
    return lib.url.fetch_json(
        metabase_url,
        header=header,
        data=data,
        encoding='serialized-json',
        insecure=args.INSECURE,
        no_proxy=args.NO_PROXY,
        timeout=args.TIMEOUT,
    )


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)

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

    token = lib.base.coe(get_token(args.URL, args.USERNAME, args.PASSWORD, args.CACHE_EXPIRE*60*60))

    # fetch data
    activity = lib.base.coe(get_json(args.URL + '/api/activity', token))[0]

    settings = lib.base.coe(get_data(args.URL + '/api/setting/site-name', token))  # /api/setting/:key returns plain text, not json
    stats = lib.base.coe(get_json(args.URL + '/api/util/stats', token))

    # build the message
    msg += '{} on '.format(settings)
    msg += 'Metabase {}; '.format(stats['version'])

    msg += '{} {}, '.format(stats['stats']['user']['users']['total'], lib.txt.pluralize('user', stats['stats']['user']['users']['total']))
    msg += '{} {} analyzed, '.format(stats['stats']['database']['databases']['analyzed'], lib.txt.pluralize('DB', stats['stats']['database']['databases']['analyzed']))
    msg += '{} {} (GUI), '.format(stats['stats']['question']['questions']['gui'], lib.txt.pluralize('question', stats['stats']['question']['questions']['gui']))
    msg += '{} {}, '.format(stats['stats']['alert']['alerts'], lib.txt.pluralize('alert', stats['stats']['alert']['alerts']))
    msg += '{} {}, '.format(stats['stats']['pulse']['pulses'], lib.txt.pluralize('pulse', stats['stats']['pulse']['pulses']))
    msg += '{} {}; '.format(stats['stats']['collection']['collections'], lib.txt.pluralize('collection', stats['stats']['collection']['collections']))
    msg += '{} CPUs, {} MiB RAM'.format(stats['stats']['system']['processors'], stats['stats']['system']['max_memory'])

    msg += u'\nLast activity: "{}{}" by {} ({} ago)'.format(
        activity['topic'],
        '/' + activity['details']['name'] if 'name' in activity['details'] else '',
        activity['user']['common_name'],
        lib.human.seconds2human(lib.time.timestrdiff(lib.time.now(as_type='iso'), activity['timestamp'].replace('T', ' '))),
        )

    perfdata += lib.base.get_perfdata('alerts', stats['stats']['alert']['alerts'], None, None, None, 0, None)
    perfdata += lib.base.get_perfdata('collections', stats['stats']['collection']['collections'], None, None, None, 0, None)
    perfdata += lib.base.get_perfdata('cpu', stats['stats']['system']['processors'], None, None, None, 0, None)
    perfdata += lib.base.get_perfdata('dbs_analyzed', stats['stats']['database']['databases']['total'], None, None, None, 0, None)
    perfdata += lib.base.get_perfdata('pulses', stats['stats']['pulse']['pulses'], None, None, None, 0, None)
    perfdata += lib.base.get_perfdata('questions_gui', stats['stats']['question']['questions']['gui'], None, None, None, 0, None)
    perfdata += lib.base.get_perfdata('ram', stats['stats']['system']['max_memory'], 'MB', None, None, 0, None)
    perfdata += lib.base.get_perfdata('users', stats['stats']['user']['users']['total'], None, None, None, 0, None)

    # over and out
    lib.base.oao(msg, state, perfdata)


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