#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Continuous integration toolkit. The purpose of this script is to generate
everything which is needed for the CI environment.

List of responsibilities:
    * execute default suites
    * terminate execution if any of internal scenarios fails
    * creates the report file that is needed by the Apollo plugin
      to post the status on Phabricator. (.quality_assurance_status)
"""

import argparse
import atexit
import copy
import json
import os
import subprocess
import sys
import tempfile
import time

import yaml

SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
TESTS_DIR = os.path.join(SCRIPT_DIR, "tests")
BASE_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, "..", ".."))
BUILD_DIR = os.path.join(BASE_DIR, "build")


def wait_for_server(port, delay=0.01):
    cmd = ["nc", "-z", "-w", "1", "127.0.0.1", str(port)]
    count = 0
    while subprocess.call(cmd) != 0:
        time.sleep(0.01)
        if count > 20 / 0.01:
            print("Could not wait for server on port", port, "to startup!")
            sys.exit(1)
        count += 1
    time.sleep(delay)


def generate_result_csv(suite, result_path):
    if not os.path.exists(result_path):
        return ""
    with open(result_path) as f:
        result = json.load(f)
    ret = ""
    for i in ["total", "passed"]:
        ret += "{},{},{}\n".format(suite, i, result[i])
    return ret


def generate_status(suite, result_path, required):
    if not os.path.exists(result_path):
        return ("Internal error!", 0, 1)
    with open(result_path) as f:
        result = json.load(f)
    total = result["total"]
    passed = result["passed"]
    ratio = passed / total
    msg = "{} / {} ({:.2%})".format(passed, total, ratio)
    if required:
        if passed == total:
            msg += " &#x1F44D;"  # +1 emoji
        else:
            msg += " &#x26D4;"  # no entry emoji
    return (msg, passed, total)


def generate_result_html(data):
    ret = "<!DOCTYPE html>\n"
    ret += "<h1>Memgraph GQL Behave Tests Status</h1>\n"
    ret += "<table>\n"
    for row in data:
        ret += "  <tr>\n"
        for item in row:
            if row == data[0]:
                fmt = "    <th>{}</th>\n"
            else:
                fmt = "    <td>{}</td>\n"
            ret += fmt.format(item)
        ret += "  </tr>\n"
    ret += "</table>\n"
    return ret


class MemgraphRunner:
    def __init__(self, build_directory):
        self.build_directory = build_directory
        self.proc_mg = None
        self.args = []

    def start(self, args=[]):
        if args == self.args and self.is_running():
            return

        self.stop()
        self.args = copy.deepcopy(args)

        self.data_directory = tempfile.TemporaryDirectory()
        memgraph_binary = os.path.join(self.build_directory, "memgraph")
        args_mg = [
            memgraph_binary,
            "--storage-properties-on-edges",
            "--data-directory",
            self.data_directory.name,
            "--log-file",
            str(os.path.join(BASE_DIR, "tests", "gql_behave", "memgraph.log")),
        ]
        self.proc_mg = subprocess.Popen(args_mg + self.args)
        wait_for_server(7687, 1)
        assert self.is_running(), "The Memgraph process died!"

    def is_running(self):
        if self.proc_mg is None:
            return False
        if self.proc_mg.poll() is not None:
            return False
        return True

    def stop(self):
        if not self.is_running():
            return
        self.proc_mg.terminate()
        code = self.proc_mg.wait()
        assert code == 0, "The Memgraph process exited with non-zero!"


def main():
    # Parse args
    argp = argparse.ArgumentParser()
    argp.add_argument("--build-directory", default=BUILD_DIR)
    argp.add_argument("--cluster-size", default=3, type=int)
    args = argp.parse_args()

    # Load tests from config file
    with open(os.path.join(TESTS_DIR, "config.yaml")) as f:
        suites = yaml.safe_load(f)

    # venv used to run the qa engine
    venv_python = os.path.join(BASE_DIR, "tests", "ve3", "bin", "python3")

    # Temporary directory for suite results
    output_dir = tempfile.TemporaryDirectory()

    # Memgraph runner
    memgraph = MemgraphRunner(args.build_directory)

    @atexit.register
    def cleanup():
        memgraph.stop()

    # Results storage
    result_csv = "suite,status,quantity\n"
    status_data = [["Suite", "Status"]]
    mandatory_fails = []

    # Run suites
    old_storage_mode = ""
    for suite in suites:
        print("Starting suite '{}' scenarios.".format(suite["name"]))

        suite_storage_mode = suite["storage_mode"]
        if old_storage_mode != suite_storage_mode:
            memgraph.stop()
            memgraph.start(["--storage-mode", suite_storage_mode])
            old_storage_mode = suite_storage_mode

        suite["stats_file"] = os.path.join(output_dir.name, suite["name"] + ".json")
        cmd = [
            venv_python,
            "-u",
            os.path.join(SCRIPT_DIR, "run.py"),
            "--stats-file",
            suite["stats_file"],
            suite["test_suite"],
        ]

        # The exit code isn't checked here because the `behave` framework
        # returns a non-zero exit code when some tests fail.
        subprocess.run(cmd)

        suite_status, suite_passed, suite_total = generate_status(
            suite["name"], suite["stats_file"], suite["must_pass"]
        )

        status_data.append([suite["name"], suite_status])
        result_csv += generate_result_csv(suite["name"], suite["stats_file"])

        if suite["must_pass"] and suite_passed != suite_total:
            mandatory_fails.append(suite["name"])
            break

    # Create status message
    result_html = generate_result_html(status_data)

    # Create the report file
    result_html_path = os.path.join(SCRIPT_DIR, "gql_behave_status.html")
    with open(result_html_path, "w") as f:
        f.write(result_html)

    # Create the measurements file
    result_csv_path = os.path.join(SCRIPT_DIR, "gql_behave_status.csv")
    with open(result_csv_path, "w") as f:
        f.write(result_csv)

    print(f"CSV status is generated in {result_csv_path}")
    print(f"HTML status is generated in {result_html_path}")

    # Check if tests failed
    if mandatory_fails != []:
        sys.exit(f"Some tests that must pass have failed: {mandatory_fails}")


if __name__ == "__main__":
    main()
