#!/usr/bin/env python3

# fetch metadata from dory, and format it for the scamper python api

import ipaddress
import json
import os
import sys
import time
from unidecode import unidecode

import caida_oidc_client
from requests_oauthlib import OAuth2Session

OUTFILE = "/etc/ark/fireball.meta"

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 = os.path.join(os.environ['HOME'], ".arkmon-offline.token")

# metadata is stored in scamper something like this:
#   char              *name;
#   char              *ipv4;
#   char              *asn4;
#   char              *cc;
#   char              *st;
#   char              *place;
#   char              *latlong;
#   char              *shortname;
#
# and stored in the metadata file like this:
#   hlz2-nz.ark.caida.org cc nz
#   hlz2-nz.ark.caida.org st wko
#   hlz2-nz.ark.caida.org shortname hlz2-nz
#   hlz2-nz.ark.caida.org place Hamilton
#
# notes:
#   * all ascii text, convert utf8 characters
#   * just pick the first asn for now, if there are multiple
#   * combine lat/long into a comma separated string
#   * don't set anything that doesn't have a value

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


def per_monitor_metadata(monitor):
    """ Extract and format the metadata from a single monitor dictionary """
    if monitor.get("node") is None:
        return None

    metadata = {
        "name": f"{monitor['node']}.ark.caida.org",
        "shortname": monitor["node"],
    }

    if country := monitor.get("country"):
        metadata["cc"] = unidecode(country)
    if state := monitor.get("state"):
        metadata["st"] = unidecode(state)
    if city := monitor.get("city"):
        metadata["place"] = unidecode(city)
    if (lat := monitor.get("latitude")) and (long := monitor.get("longitude")):
        metadata["latlong"] = f"{float(lat):.2f},{float(long):.2f}"
    if ipv4 := monitor.get("ipv4global"):
        metadata["ipv4"] = ipv4
    if (ipv6 := monitor.get("ipv6global", monitor.get("ipv6local"))):
        if ipaddress.ip_address(ipv6).is_global:
            metadata["ipv6"] = ipv6
    if asn4 := monitor.get("asn"):
        if isinstance(asn4, str):
            metadata["asn4"] = asn4.split("_")[0]
        else:
            metadata["asn4"] = asn4

    return metadata


def get_metadata():
    """ Get a list of metadata for all valid monitors """
    monitors = ark_dory_query()
    if monitors is None or len(monitors) == 0:
        return None
    return [y for x in monitors if (y := per_monitor_metadata(x))]


def write_metadata(metadata, filename):
    """ Write all the metadata to a file in the fireball text format """
    with open(filename, "w", encoding="ascii") as outfile:
        for monitor in metadata:
            for key, value in monitor.items():
                if key != "name":
                    outfile.write(f"{monitor['name']} {key} {value}\n")


def main():
    metadata = get_metadata()
    if not metadata:
        print("Failed to fetch metadata")
        sys.exit(1)
    write_metadata(metadata, OUTFILE)


if __name__ == "__main__":
    main()
