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

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."""

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=f'%(prog)s: v{__version__} by {__author__}',
    )

    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-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(
        '--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(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, 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(
        '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. This is where the magic happens."""

    # parse the command line
    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 += 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'

    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', ' '),
        ),
    )
    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)


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