#!/usr/lib64/linuxfabrik-monitoring-plugins/venv/bin/python
# -*- 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.cache
import lib.human
import lib.time
import lib.txt
import lib.url
from lib.globals import STATE_OK, STATE_UNKNOWN

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

DESCRIPTION = """Retrieves recent activity and usage statistics from a Metabase instance via its API.
Reports active users, executed queries, dashboards, and other operational metrics.
Credentials are cached to reduce API calls.
The figures are reported for trending and never alert on their own."""

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 = 3
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,
        epilog=lib.args.epilog(__file__),
        formatter_class=lib.args.HelpFormatter,
    )

    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(
        '--cache-expire',
        help='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=lib.args.help('--critical') + ' Default: >= %(default)s',
        dest='CRIT',
        type=int,
        default=DEFAULT_CRIT,
    )

    parser.add_argument(
        '--insecure',
        help=lib.args.help('--insecure'),
        dest='INSECURE',
        action='store_true',
        default=DEFAULT_INSECURE,
    )

    parser.add_argument(
        '--no-perfdata',
        help=lib.args.help('--no-perfdata'),
        dest='NO_PERFDATA',
        action='store_true',
        default=False,
    )

    parser.add_argument(
        '--no-proxy',
        help=lib.args.help('--no-proxy'),
        dest='NO_PROXY',
        action='store_true',
        default=DEFAULT_NO_PROXY,
    )

    parser.add_argument(
        '-p',
        '--password',
        help='Password for authenticating against the Metabase API.',
        dest='PASSWORD',
        required=True,
    )

    parser.add_argument(
        '--proxy',
        help=lib.args.help('--proxy'),
        dest='PROXY',
        default=None,
    )

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

    parser.add_argument(
        '--url',
        help='Base URL of the Metabase instance. Default: %(default)s',
        dest='URL',
        default=DEFAULT_URL,
    )

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

    parser.add_argument(
        '-w',
        '--warning',
        help=lib.args.help('--warning') + ' Default: >= %(default)s',
        dest='WARN',
        type=int,
        default=DEFAULT_WARN,
    )

    args, _ = parser.parse_known_args()
    return args


def get_token(args, 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": "linuxfabrik"}' \\
    $     http://localhost:3000/api/session
    """
    # we cache credentials to reuse them until they expire, because logins are
    # rate-limited for security. The key names the instance and the account, so a
    # token is only ever sent back to the server that issued it, and only on behalf
    # of the user it was issued to.
    cache_key = f'metabase-token-{metabase_url}-{user}'
    token = lib.cache.get(
        cache_key, 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,
        proxy=args.PROXY,
        timeout=args.TIMEOUT,
    )
    if not success:
        return (success, result)
    if not result:
        return (False, f'There was no result from {metabase_url}.')
    if 'id' not in result:
        return (False, 'Something went wrong, maybe wrong username/password.')
    token = result['id']
    lib.cache.set(
        cache_key,
        token,
        lib.time.now() + expire,
        filename='linuxfabrik-monitoring-plugins-metabase-stats.db',
    )
    return (True, token)


def get_data(args, metabase_url, token, data=None):
    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,
        proxy=args.PROXY,
        timeout=args.TIMEOUT,
    )


def get_json(args, metabase_url, token, data=None):
    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,
        proxy=args.PROXY,
        timeout=args.TIMEOUT,
    )


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
    token = lib.base.coe(
        get_token(
            args, args.URL, args.USERNAME, args.PASSWORD, args.CACHE_EXPIRE * 60 * 60
        )
    )
    activity = lib.base.coe(get_json(args, args.URL + '/api/activity', token))[0]

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

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

    # analyze data
    detail_name = (
        '/' + activity['details']['name'] if 'name' in activity['details'] else ''
    )
    time_ago = lib.human.seconds2human(
        lib.time.timestrdiff(
            lib.time.now(as_type='iso'),
            activity['timestamp'].replace('T', ' '),
        ),
    )

    # build the message
    msg += f'{settings} on '
    msg += f'Metabase {stats["version"]}; '

    msg += f'{stats["stats"]["user"]["users"]["total"]} {lib.txt.pluralize("user", stats["stats"]["user"]["users"]["total"])}, '
    msg += f'{stats["stats"]["database"]["databases"]["analyzed"]} {lib.txt.pluralize("DB", stats["stats"]["database"]["databases"]["analyzed"])} analyzed, '
    msg += f'{stats["stats"]["question"]["questions"]["gui"]} {lib.txt.pluralize("question", stats["stats"]["question"]["questions"]["gui"])} (GUI), '
    msg += f'{stats["stats"]["alert"]["alerts"]} {lib.txt.pluralize("alert", stats["stats"]["alert"]["alerts"])}, '
    msg += f'{stats["stats"]["pulse"]["pulses"]} {lib.txt.pluralize("pulse", stats["stats"]["pulse"]["pulses"])}, '
    msg += f'{stats["stats"]["collection"]["collections"]} {lib.txt.pluralize("collection", stats["stats"]["collection"]["collections"])}; '
    msg += f'{stats["stats"]["system"]["processors"]} CPUs, {stats["stats"]["system"]["max_memory"]} MiB RAM'
    msg += (
        f'\nLast activity:'
        f' "{activity["topic"]}{detail_name}"'
        f' by {activity["user"]["common_name"]}'
        f' ({time_ago} ago)'
    )

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

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


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