#!/usr/bin/env python3

import copy
import os
from pathlib import Path
import subprocess
import sys
import unittest
import shutil
import io

FIXTURES = Path.cwd() / "tests/fixtures"
WORKSPACE = Path(os.getenv("WORKSPACE", "/tmp")).absolute()
FAKE_BIN = WORKSPACE / "bin"
FAKE_PATH = "%s:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin" % FAKE_BIN
FAKE_CMD_LIST = WORKSPACE / "cmd_list"
FAKE_CMD = """
WORKSPACE="%s"
OUTPUT="%s"
prog=$(echo $0|sed s#${WORKSPACE}/##)
read -t0.01 input
if [ -z "${input}" ] ; then
  echo ${prog} $* >> ${OUTPUT}
else
  echo ${prog} $* \\< "${input}" >> ${OUTPUT}
fi
"""


def executeAndReturnOutput(command, env):
    p = subprocess.Popen(
        command,
        encoding="utf-8",
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        env=env,
    )
    stdoutdata, stderrdata = p.communicate()
    print(stdoutdata, file=sys.stdout)
    print(stderrdata, file=sys.stderr)
    return p.returncode, stdoutdata, stderrdata


def create_prog(filename, command):
    """Create test program.

    :param unicode filename: destination filename
    :param unicode command: command to write to test program
    """
    with io.open(filename, "w", encoding="utf-8") as fp:
        fp.write("#!/bin/bash\n%s\n" % (command,))
        os.fchmod(fp.fileno(), 0o755)


command = [
    "env",
    "PATH=%s" % FAKE_PATH,
    "/bin/bash",
    "./ngcp-reset-db",
]


class TestCommandLine(unittest.TestCase):
    def _create_cmds(self):
        for prog in [
            "ngcp-service",
            "mysqldump",
            "mysql",
            "ngcp-update-db-schema",
            "ngcp-sync-db-grants",
            "ngcp-sync-db-creds",
            "ngcp-ssh",
        ]:
            create_prog(FAKE_BIN / prog, FAKE_CMD % (FAKE_BIN, FAKE_CMD_LIST))
        create_prog(
            FAKE_BIN / "ngcp-nodename",
            "#!/bin/bash\nsource ${NGCP_ROLES}\necho ${NGCP_NODENAME}",
        )

    def checkCmd(self, f):
        res = executeAndReturnOutput(["diff", "-uN", FAKE_CMD_LIST, f], None)
        self.assertEqual(res[0], 0, res[1])

    def runCommandAsRoot(self):
        if not self.is_root:
            self.skipTest("command requires root")
        return executeAndReturnOutput(self.command, self.env)

    def setUp(self):
        self.is_root = os.geteuid() == 0
        self.command = copy.deepcopy(command)
        self.env = {
            "db_config_file": FIXTURES / "ngcp-reset-db.conf",
            "NGCP_ROLES": FIXTURES / "ngcp-roles.ce",
            "BACKUP": WORKSPACE,
            "NGCP_DB_SCHEMA": FIXTURES,
        }
        if not FAKE_BIN.exists():
            os.makedirs(FAKE_BIN)
        self._create_cmds()

    def tearDown(self):
        if WORKSPACE.exists():
            shutil.rmtree(WORKSPACE)

    def testHelp(self):
        self.command.append("-h")
        res = executeAndReturnOutput(self.command, self.env)
        self.assertEqual(res[0], 0)
        self.assertRegex(res[1], "Usage:")

    def testLongHelp(self):
        self.command.append("--help")
        res = executeAndReturnOutput(self.command, self.env)
        self.assertEqual(res[0], 0)
        self.assertRegex(res[1], "Usage:")

    def testForce(self):
        self.command.append("-f")
        res = self.runCommandAsRoot()
        self.assertEqual(res[0], 0)
        self.assertRegex(res[1], "Running in automated mode")
        self.checkCmd(FIXTURES / "ngcp-reset-db.ce")

    def testLongForce(self):
        self.command.append("--force")
        res = self.runCommandAsRoot()
        self.assertEqual(res[0], 0)
        self.assertRegex(res[1], "Running in automated mode")
        self.checkCmd(FIXTURES / "ngcp-reset-db.ce")

    def testLocalOnly(self):
        self.env["FORCE_RESET"] = "yes"
        self.command.append("-l")
        res = self.runCommandAsRoot()
        self.assertEqual(res[0], 0)
        self.assertRegex(res[1], "Skipping execution on neighbours")
        self.checkCmd(FIXTURES / "ngcp-reset-db.ce")

    def testLocalOnlyPRO(self):
        self.env["FORCE_RESET"] = "yes"
        self.env["NGCP_ROLES"] = FIXTURES / "ngcp-roles.pro"
        self.command.append("-l")
        res = self.runCommandAsRoot()
        self.assertEqual(res[0], 0)
        self.assertRegex(res[1], "Skipping execution on neighbours")
        self.checkCmd(FIXTURES / "ngcp-reset-db.pro_local")

    def testPRO(self):
        self.env["FORCE_RESET"] = "yes"
        self.env["NGCP_ROLES"] = FIXTURES / "ngcp-roles.pro"
        res = self.runCommandAsRoot()
        self.assertEqual(res[0], 0)
        self.assertRegex(res[1], "Stopping services on the peer 'sp2'")
        self.checkCmd(FIXTURES / "ngcp-reset-db.pro")

    def testSkipStart(self):
        self.env["FORCE_RESET"] = "yes"
        self.command.append("-s")
        res = self.runCommandAsRoot()
        self.assertEqual(res[0], 0)
        self.assertRegex(res[1], "Skipping start of service")
        self.checkCmd(FIXTURES / "ngcp-reset-db.ce_skip-start")

    def testSkipStartLong(self):
        self.env["FORCE_RESET"] = "yes"
        self.command.append("--skip-start")
        res = self.runCommandAsRoot()
        self.assertEqual(res[0], 0)
        self.assertRegex(res[1], "Skipping start of service")
        self.checkCmd(FIXTURES / "ngcp-reset-db.ce_skip-start")

    def testSkipBackup(self):
        self.env["FORCE_RESET"] = "yes"
        self.command.append("-k")
        res = self.runCommandAsRoot()
        self.assertEqual(res[0], 0)
        self.assertRegex(res[1], "Skipping backup process")
        self.checkCmd(FIXTURES / "ngcp-reset-db.ce_skip-backup")

    def testSkipBackupLong(self):
        self.env["FORCE_RESET"] = "yes"
        self.command.append("--skip-backup")
        res = self.runCommandAsRoot()
        self.assertEqual(res[0], 0)
        self.assertRegex(res[1], "Skipping backup process")
        self.checkCmd(FIXTURES / "ngcp-reset-db.ce_skip-backup")

    def testSkipAll(self):
        self.command.append("--force")
        self.command.append("--local-only")
        self.command.append("--skip-start")
        self.command.append("--skip-backup")
        res = self.runCommandAsRoot()
        self.assertEqual(res[0], 0)
        self.assertRegex(res[1], "Skipping execution on neighbours")
        self.assertRegex(res[1], "Skipping start of service")
        self.assertRegex(res[1], "Skipping backup process")
        self.checkCmd(FIXTURES / "ngcp-reset-db.ce_skip-all")

    def testSkipAllPRO(self):
        self.env["NGCP_ROLES"] = FIXTURES / "ngcp-roles.pro"
        self.command.append("--force")
        self.command.append("--local-only")
        self.command.append("--skip-start")
        self.command.append("--skip-backup")
        res = self.runCommandAsRoot()
        self.assertEqual(res[0], 0)
        self.assertRegex(res[1], "Skipping execution on neighbours")
        self.assertRegex(res[1], "Skipping start of service")
        self.assertRegex(res[1], "Skipping backup process")
        self.checkCmd(FIXTURES / "ngcp-reset-db.pro_skip-all")
