#!/usr/bin/python3

# (c) Copyright ESO 2024
# All Rights Reserved
# ESO (eso.org) is an Intergovernmental Organisation, and therefore special legal conditions apply.

from cii.mgmt import (
    host_resolve,
    redis_raw,
    config_discover,
    oldb_discover,
    config_impex,
    config_init,
    oldb_init
)
from elt.oldb import CiiOldbGlobal

import os
import argparse
import tempfile


def discover_and_connect():

    # connect to config-storage

    conf_loc = config_discover.locate_config_storage()
    conf_host,conf_port = conf_loc.address.split(':')

    # disaster prevention: to prevent users from inadvertently
    # resetting a remote oldb, this tool will only run on the
    # host that holds the config-redis.
    #
    # 2024-04: host name resolution does not work in mock environment, we now
    #          hard-code localhost. This works with all our redis-deployments.
    #          Since we cannot pre-determine if we are on the config-host, we
    #          can only try to connect and wait for an exception.
    if conf_host != 'localhost' and conf_host != "127.0.0.1":
        print(f"Note: For disaster prevention, this command can only be run on the config-storage host.")
        print(f"Autofix: The discovered hostname '{conf_host}' will be ignored and replaced with 'localhost'")
        conf_host = 'localhost'

    conf_client = redis_raw.redis_client (conf_host, conf_port)


    # connect to live-value-storages

    value_clients = []
    redis_locs = oldb_discover.read_locations (conf_client)
    for addr in redis_locs.dpredises + redis_locs.extredises:
        try:
            host,port = (addr.split(':')[1], addr.split(':')[2])
            client = redis_raw.redis_client (host, port)
            value_clients.append (client)
        except Exception as e:
            print (f"Note: Skipping value-redis {host}:{port}, due to", e)


    # succeeded

    return conf_client, value_clients


def reset (conf_client, value_clients):
    ''' Delete datapoints etc., but preserve custom config '''

    # find and delete live-values
    # ---------------------------
    # With ECII-903, it would not be necessary to delete the live values, as oldb api
    # will ignore a live value that has no accompanying meta-data. we're still doing
    # it here for two reasons: i) at the time of writing 903 is not yet fully implemented,
    # ii) the orphaned live values are bloat, and removing them makes the database cleaner.

    for client in value_clients:
        for sc in redis_raw.redis_single_clients (client):
            dbindex1 = CiiOldbGlobal.OLDB_DATAPOINT_PREFIX+'*'
            dbindex2 = CiiOldbGlobal.OLDB_DATAPOINT_SLICE_PREFIX+'*'
            keys = sc.keys(dbindex1) + sc.keys(dbindex2)
            if keys: # cannot call delete without args
                sc.delete(*keys)

    # clean up oldb configuration
    # ---------------------------
    # The oldb configuration may well be custom (different from the
    # factory settings). Oppeosed to the nuke function, we preserve
    # it. But if it has multiple versions, we remove those.

    with tempfile.TemporaryDirectory() as tmpdir:
        #tmpdir = 'tmp'  # debug

        # rescue selected documents from config-storage, e.g.:
        #   tmp/configuration_instance/oldb/calculationNodes%1
        #   tmp/configuration_instance/oldb/configurations/oldbClientConfig%2
        #   tmp/configuration_instance/oldb/configurations/oldbclientconfig.d/testext2%1

        config_impex.download (conf_client, ['ci'], 'oldb/calculationNodes', tmpdir, True)
        config_impex.download (conf_client, ['ci'], 'oldb/configurations',   tmpdir, True)

        # remove all metadata
        for sc in redis_raw.redis_single_clients (conf_client):
            host,port = redis_raw.client_target_address (sc)
            host='localhost' # init-scripts have a disaster-check now
            serv_addr = f'{host}:{port}'
            config_init.main( [serv_addr, '-s', '--do', 'clean'] )

        # recreate metadata
        sc = redis_raw.redis_single_clients(conf_client)[0]
        host,port = redis_raw.client_target_address (sc)
        host='localhost' # init-scripts have a disaster-check now
        serv_addr = f'{host}:{port}'
        config_init.main( [serv_addr, '-s', '--do', 'metadata'] )
        oldb_init.main  ( [serv_addr,       '--do', 'metadata'] )

        # rewrite history: start at version 1
        for dirname, dirs, files in os.walk(tmpdir):
            for name in files:
                pos = name.rindex('%')
                version = int(name[pos+1])
                if version > 1:
                    newname = name[:pos+1]+'1'
                    os.rename (os.path.join(dirname, name), os.path.join(dirname, newname))

        # reinstate the documents
        config_impex.upload (conf_client, ['ci'], '', tmpdir)

        # impl-note: the calc-nodes hold subscriptions on
        # the datapoints, but restarting them turned out
        # unnecessary, as their subscriptions will survive.


def nuke (conf_client, value_clients):
    ''' Back to factory settings (like after first cii-postinstall) '''

    # delete all keys
    # ---------------------------

    for client in value_clients:
        singles = redis_raw.redis_single_clients (client)

        for sc in singles:
            sc.flushall()

    # there is no reasonable default we could put.
    # we offload the rest to the user, giving some advice.



def go() -> bool:

    parser = argparse.ArgumentParser(prog='oldbReset',
        description = 'Wipe the OLDb, directly accessing the underlying databases',
             epilog = 'Note: This tool can only be executed on the config-server host')
    parser.add_argument('--nuke', action='store_true',
               help = 'Complete wipe, reset back to square one')
    args = parser.parse_args()

    print ("Connecting ...")
    conf_client, value_clients = discover_and_connect()


    # make sure users understand what they are doing
    # ----------------------------------------------

    tmp = set()
    for c in [ conf_client ] + value_clients:
        for sc in redis_raw.redis_single_clients(c):
            h,p = redis_raw.client_target_address (sc)
            tmp.add(' - '+h+':'+str(p))
    affected_databases = '\n'+ ('\n'.join(tmp))

    if not args.nuke:
        if input(f'''
This will remove all datapoints and their metadata from the OLDB,
as well as any older versions of OLDB configuration, retaining
only the latest version. It will also refresh the built-in OLDB
datatypes back to their defaults.

The following databases will be cleaned: {affected_databases}

Please confirm this is what you intend [N/yes] ''') != 'yes': return False

        reset (conf_client, value_clients)


    if args.nuke:
        if input(f'''
Since you have specified the nuke option, this will fully wipe
all (also OLDB-unrelated) content from the databases listed below.
This includes removing all existing OLDB configuration. After that,
you can (and must) redo the OLDB set-up from scratch using
cii-postinstall (will require root privileges).

The following databases will be emptied: {affected_databases}

Please confirm this is what you intend [N/YES] ''') != 'YES': return False

        nuke (conf_client, value_clients)

        print ('''

OLDB was fully wiped and is therefore currently not functional.
Next, set up the OLDB again (as root):
  $ cii-postinstall knownrole

After that, also remember to re-run any other tools you
  previously used to set up your OLDB, e.g. oldbAdmin,
  oldb-cluster-ctl, etc.''')

    return True


if __name__ == "__main__":

    try:
        x = go()
        print ("\nOLDB has been reset" if x else "\nNo changes were made")
        exit (0)
    except Exception as e:
        print ("Error occurred:", e)
        exit (1)

