#!/usr/bin/python3

"""
This test checks the correctness of a leader election process when its decoupled
from log replication. In other words, in this test we do not change the state of
the database, i.e., the Raft log remains empty.

The test proceeds as follows for clusters of size 3 and 5:
    1) Start a random subset of workers in the cluster
    2) Check if the leader has been elected
    3) Kill all living workers
    4) GOTO 1) and repeat 10 times

Naturally, throughout the process we keep track the number of alive workers and,
based on that number (majority or not), we decide whether the leader should have
been elected.
"""

import argparse
import os
import time
import random
import subprocess
import sys
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
PROJECT_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, "..", "..", "..", ".."))

# append parent directory
sys.path.append(os.path.join(SCRIPT_DIR, ".."))

from ha_test import HaTestBase

def random_subset(l):
    return random.sample(l, random.randint(0, len(l) - 1))


class HaLeaderElectionTest(HaTestBase):
    def leader_check(self, has_majority):
        client = subprocess.Popen([self.tester_binary,
            "--has-majority", str(int(has_majority)),
            "--cluster-size", str(int(cluster_size))])

        # Check what happened with query execution.
        try:
            code = client.wait(timeout=60)
        except subprocess.TimeoutExpired as e:
            print("Error! client timeout expired.")
            client.kill()
            return 1

        return code


    def execute(self):
        for i in range(10):
            # Awake random subset of dead nodes
            alive = random_subset(range(0, self.cluster_size))
            for wid in alive:
                print("Starting worker {}".format(wid + 1))
                self.start_worker(wid)

            # Check if leader is elected
            assert self.leader_check(2 * len(alive) > self.cluster_size) == 0, \
                    "Error while trying to find leader"

            # Kill living nodes
            for wid in alive:
                print("Killing worker {}".format(wid + 1))
                self.kill_worker(wid)

        self.destroy_cluster()


def find_correct_path(path):
    f = os.path.join(PROJECT_DIR, "build", path)
    if not os.path.exists(f):
        f = os.path.join(PROJECT_DIR, "build_debug", path)
    return f


if __name__ == "__main__":
    memgraph_binary = find_correct_path("memgraph_ha")
    tester_binary = find_correct_path(os.path.join("tests", "integration", "ha",
        "leader_election", "tester"))

    raft_config_file = os.path.join(PROJECT_DIR, "tests", "integration", "ha",
        "leader_election", "raft.json")

    parser = argparse.ArgumentParser()
    parser.add_argument("--memgraph", default=memgraph_binary)
    parser.add_argument("--raft_config_file", default=raft_config_file)
    args = parser.parse_args()

    for cluster_size in [3, 5]:
        print("\033[1;36m~~ Executing test with cluster size: %d~~\033[0m" % (cluster_size))
        HaLeaderElectionTest(
            args.memgraph, tester_binary, args.raft_config_file, cluster_size)
        print("\033[1;32m~~ The test finished successfully ~~\033[0m")

    sys.exit(0)
