#!/usr/bin/env python3

import json
import math
import os
import sys
import time
from functools import cmp_to_key

import caida_oidc_client
from requests_oauthlib import OAuth2Session

REALM = "CAIDA"
API_URL = "https://api.arkmon.caida.org"
AUTH_URL = f"https://auth.caida.org/realms/{REALM}/protocol/openid-connect"
CLIENT_ID = "arkmon-offline"
TOKEN_FILE = "/etc/ark/.arkmon-offline.token"


def load_token_info(token_file):
    with open(token_file, "r", encoding="ascii") as f:
        token_info = json.load(f)
    if "expires_at" not in token_info or token_info["expires_at"] < time.time():
        refresh_token = token_info['refresh_token']
        token_info.clear()
        token_info['refresh_token'] = refresh_token
        token_info['expires_in'] = -1
        token_info['access_token'] = 'dummy value for oauthlib'
    return token_info


def ark_dory_query():
    token_info = load_token_info(TOKEN_FILE)
    save_tokens = caida_oidc_client.make_save_tokens(TOKEN_FILE)

    # establish a new session, refreshing access token if necessary
    session = OAuth2Session(client_id=CLIENT_ID,
                token=token_info,
                auto_refresh_url=f"{AUTH_URL}/token",
                auto_refresh_kwargs={"client_id": CLIENT_ID},
                token_updater=save_tokens)

    if not session.authorized:
        print("Failed to authorize session, aborting")
        return None

    try:
        response = session.request("GET", f"{API_URL}/monitors/")
    except Exception as e:
        print(f"Failed to fetch {API_URL}/monitors: {e}")
        return None

    if response.status_code != 200:
        print(f"Got status code {response.status_code}, aborting")
        return None

    monitors = response.json()
    if len(monitors) == 0:
        print("Empty monitors list, aborting")
        return None

    return monitors


# sort VPs where NTP is not working ahead of VPs where NTP is working.
# then sort by reported offset.
def _ntpsort(x, y):
    xoffset = x.get("ntpoffset")
    xserver = x.get("ntpserver")
    yoffset = y.get("ntpoffset")
    yserver = y.get("ntpserver")

    if xserver is None and yserver is not None:
        return -1
    if xserver is not None and yserver is None:
        return 1
    if xserver is None and yserver is None:
        return 0

    if xoffset is None and yoffset is not None:
        return -1
    if xoffset is not None and yoffset is None:
        return 1
    if xoffset is None and yoffset is None:
        return 0

    if xoffset < yoffset:
        return 1
    if xoffset > yoffset:
        return -1

    return 0


def _main():
    monitors = ark_dory_query()
    if not monitors:
        return 1

    # only use monitors that were active in the last 24 hours
    cutoff = time.time() - (60 * 60 * 24)
    recent = [x for x in monitors if x.get("lastping", 0) > cutoff]

    max_bar_width = 80 - 7 - 1 - 11 - 1
    print(f"{len(recent)} Clock Offsets:")

    # print out VPs, sorted by those that need the most attention
    for monitor in sorted(recent, key=cmp_to_key(_ntpsort)):
        name = monitor.get("node")
        offset = monitor.get("ntpoffset")
        server = monitor.get("ntpserver")
        if server is None and offset is None:
            print(f"{name:7} {'':11} no data")
        elif server is None:
            print(f"{name:7} {'':11} no sync")
        elif offset is None:
            baa = ">" * max_bar_width
            print(f"{name:7} {'':11} {baa}")
        else:
            offset = offset / 1000.0
            bar_width = math.ceil(offset)
            if bar_width > max_bar_width:
                baa = ">" * max_bar_width
            else:
                baa = "#" * bar_width
            print(f"{name:7} {offset:8.3f} ms {baa}")
    return 0

if __name__ == "__main__":
    sys.exit(_main())
