#!/usr/bin/env python3
import copy
import os
import subprocess
import sys
from threading import Thread
from unittest import TestCase
from xmlrpc.server import SimpleXMLRPCServer

FIXTURES = os.path.join(os.getcwd(), "tests", "fixtures")
NETWORK_YML = os.path.join(FIXTURES, "network_carrier.yml")
CFG_BASE = os.path.join(os.getcwd(), "tests", "etc", "ngcp-system-tools")
CFG = os.path.join(CFG_BASE, "ngcp-debug-subscriber.conf")
CFG_KO = os.path.join(CFG_BASE, "ngcp-debug-subscriber.conf_not_enable")


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


command = ["./ngcp-debug-subscriber", "--config", CFG]


class TestCommandLine(TestCase):
    def setUp(self):
        self.cmd = copy.deepcopy(command)
        self.cmd.append("--debug")
        self.cmd.append("--network")
        self.cmd.append(NETWORK_YML)
        self.cmd.append("--dry-run")

    def testNoCfg(self):
        self.cmd = ["./ngcp-debug-subscriber", "--debug", "show"]
        res = executeAndReturnOutput(self.cmd)
        self.assertNotEqual(res[0], 0)
        self.assertRegex(res[2], "FileNotFoundError")

    def testNoEnable(self):
        self.cmd.append("--config")
        self.cmd.append(CFG_KO)
        self.cmd.append("show")
        res = executeAndReturnOutput(self.cmd)
        self.assertEqual(res[0], 0)
        self.assertRegex(res[1], r"kamailio\.lb\.debug_uri\.enable")

    def testNoParams(self):
        self.cmd = ["./ngcp-debug-subscriber"]
        res = executeAndReturnOutput(self.cmd)
        self.assertEqual(res[0], 0)
        self.assertRegex(res[1], "Usage:")

    def test_file_not_found(self):
        self.cmd = copy.deepcopy(command)
        self.cmd.append("show")
        res = executeAndReturnOutput(self.cmd)
        self.assertNotEqual(res[0], 0)
        msg = "could not read network file"
        self.assertRegex(res[2], msg)

    def testLBFormat(self):
        self.cmd.append("list")
        self.cmd.append("sip:lb01")
        msg = "Invalid value for 'LB'"
        res = executeAndReturnOutput(self.cmd)
        self.assertNotEqual(res[0], 0)
        self.assertRegex(res[2], msg)

    def testLBNotSharedName(self):
        self.cmd.append("list")
        self.cmd.append("lb01a")
        msg = "Invalid value for 'LB'"
        res = executeAndReturnOutput(self.cmd)
        self.assertNotEqual(res[0], 0)
        self.assertRegex(res[2], msg)
        msg = "lb01a is not a host defined at network.yml as role lb"
        self.assertRegex(res[2], msg)

    def testAddActionParams(self):
        msg = "Missing argument 'SUBSCRIBER...'"
        self.cmd.append("add")
        self.cmd.append("lb01")
        res = executeAndReturnOutput(self.cmd)
        self.assertNotEqual(res[0], 0)
        self.assertRegex(res[2], msg)

        self.cmd.append("test@domain.ko")
        res = executeAndReturnOutput(self.cmd)
        self.assertNotEqual(res[0], 0)

    def testDelActionParams(self):
        msg = "Missing argument 'SUBSCRIBER...'"
        self.cmd.append("delete")
        self.cmd.append("lb01")
        res = executeAndReturnOutput(self.cmd)
        self.assertNotEqual(res[0], 0)
        self.assertRegex(res[2], msg)

    def testSubsFormat(self):
        self.cmd.append("add")
        self.cmd.append("lb01")
        self.cmd.append("testdomain")
        self.cmd.append("prx02")
        msg = "Invalid value for 'SUBSCRIBER...'"
        res = executeAndReturnOutput(self.cmd)
        self.assertNotEqual(res[0], 0)
        self.assertRegex(res[2], msg)

    def testURIFormat(self):
        self.cmd.append("add")
        self.cmd.append("lb01")
        self.cmd.append("test@domain.ok")
        self.cmd.append("127.0.0.1")
        msg = "Invalid value for 'URI'"
        res = executeAndReturnOutput(self.cmd)
        self.assertNotEqual(res[0], 0)
        self.assertRegex(res[2], msg)

    def testURINotSharedName(self):
        self.cmd.append("add")
        self.cmd.append("lb01")
        self.cmd.append("test@domain.ok")
        self.cmd.append("prx02a")
        msg = "Invalid value for 'URI'"
        res = executeAndReturnOutput(self.cmd)
        self.assertNotEqual(res[0], 0)
        self.assertRegex(res[2], msg)
        msg = "prx02a is not a host defined at network.yml as role proxy"
        self.assertRegex(res[2], msg)

    def testListActionParams(self):
        msg = "Missing argument 'LB'"
        self.cmd.append("list")
        res = executeAndReturnOutput(self.cmd)
        self.assertNotEqual(res[0], 0)
        self.assertRegex(res[2], msg)

    def testFlushActionParams(self):
        msg = "Missing argument 'LB'"
        self.cmd.append("flush")
        res = executeAndReturnOutput(self.cmd)
        self.assertNotEqual(res[0], 0)
        self.assertRegex(res[2], msg)


class Service:
    class htable:
        @staticmethod
        def store(table):
            assert False

        @staticmethod
        def dump(table):
            return [
                {
                    "entry": 122,
                    "size": 1,
                    "slot": [
                        {
                            "name": "A@domain.ok",
                            "value": "sip:172.30.52.132:5062",
                            "type": "str",
                        }
                    ],
                }
            ]

        @staticmethod
        def sets(table, key_name, value):
            assert table == "dbguri"
            assert key_name == "test@domain.ok"
            assert value == "sip:172.30.52.132:5062"

        @staticmethod
        def delete(table, key_name):
            assert table == "dbguri"
            assert key_name == "test@domain.ok"

        @staticmethod
        def flush(table):
            assert table == "dbguri"


class FakeServer(Thread):
    ADDR = ("localhost", 8880)

    def __init__(self, *args, **kwargs):
        self.responses = kwargs.get("responses", 1)
        if "responses" in kwargs:
            kwargs.pop("responses")
        return Thread.__init__(self, *args, **kwargs)

    def run(self):
        with SimpleXMLRPCServer(self.ADDR, allow_none=True) as srv:
            print("FakeServer at {}:{}".format(self.ADDR[0], self.ADDR[1]))
            srv.register_instance(Service(), allow_dotted_names=True)
            for i in range(self.responses):
                srv.handle_request()


class TestApp(TestCase):
    REDIS_LOG = r"redis.+ cmd:{command} {key} {params} response:{response}"

    def setUp(self):
        self.cmd = copy.deepcopy(command)
        self.cmd.append("--debug")
        self.cmd.append("--no-resolve-lb")
        self.cmd.append("--network")
        self.cmd.append(NETWORK_YML)
        self.redis_params = {
            "command": None,
            "key": None,
            "params": {},
            "response": True,
        }

    def tearDown(self):
        self.fake.join(1)

    def test_list(self):
        self.cmd.append("list")
        self.cmd.append("{0[0]}:{0[1]}".format(FakeServer.ADDR))
        self.fake = FakeServer()
        self.fake.start()
        res = executeAndReturnOutput(self.cmd)
        self.fake.join(1)
        self.assertEqual(res[0], 0)
        self.assertRegex(res[1], "A@domain.ok => sip:172.30.52.132:5062")
        self.assertNotRegex(res[1], "redis.+ cmd:")

    def test_add(self):
        self.cmd.append("add")
        self.cmd.append("--redis-host")
        self.cmd.append(FakeServer.ADDR[0])
        self.cmd.append("{0[0]}:{0[1]}".format(FakeServer.ADDR))
        self.cmd.append("test@domain.ok")
        self.cmd.append("prx01")
        self.fake = FakeServer()
        self.fake.start()
        res = executeAndReturnOutput(self.cmd)
        self.fake.join(1)
        self.assertEqual(res[0], 0)
        self.redis_params["command"] = "hset"
        self.redis_params["key"] = "debug_uri:entry::test@domain.ok"
        self.redis_params["params"] = {
            "key_name": "test@domain.ok",
            "key_type": 0,
            "value_type": 0,
            "key_value": "sip:172.30.52.132:5062",
        }
        # hset returns the count of new fields added (int),
        # not True like old hmset
        self.redis_params["response"] = r"\d+"
        self.assertRegex(res[1], self.REDIS_LOG.format(**self.redis_params))

    def test_del(self):
        self.cmd.append("delete")
        self.cmd.append("--redis-host")
        self.cmd.append(FakeServer.ADDR[0])
        self.cmd.append("{0[0]}:{0[1]}".format(FakeServer.ADDR))
        self.cmd.append("test@domain.ok")
        self.fake = FakeServer()
        self.fake.start()
        res = executeAndReturnOutput(self.cmd)
        self.fake.join(1)
        self.assertEqual(res[0], 0)
        self.redis_params["command"] = "delete"
        self.redis_params["key"] = "debug_uri:entry::test@domain.ok"
        self.redis_params["response"] = 1
        self.assertRegex(res[1], self.REDIS_LOG.format(**self.redis_params))

    def test_flush(self):
        self.cmd.append("flush")
        self.cmd.append("--redis-host")
        self.cmd.append(FakeServer.ADDR[0])
        self.cmd.append("{0[0]}:{0[1]}".format(FakeServer.ADDR))
        self.fake = FakeServer(responses=2)
        self.fake.start()
        res = executeAndReturnOutput(self.cmd)
        self.fake.join(1)
        self.assertEqual(res[0], 0)
        self.redis_params["command"] = "flushdb"
        self.assertRegex(res[1], self.REDIS_LOG.format(**self.redis_params))

    def test_add_no_store(self):
        self.cmd.append("add")
        self.cmd.append("--no-store")
        self.cmd.append("{0[0]}:{0[1]}".format(FakeServer.ADDR))
        self.cmd.append("test@domain.ok")
        self.cmd.append("prx01")
        self.fake = FakeServer()
        self.fake.start()
        res = executeAndReturnOutput(self.cmd)
        self.fake.join(1)
        self.assertEqual(res[0], 0)
        self.assertNotRegex(res[1], "redis.+ cmd:")


class TestXMLApp(TestCase):
    def setUp(self):
        self.cmd = copy.deepcopy(command)
        self.cmd.append("--dry-run")
        self.cmd.append("--debug")
        self.cmd.append("--network")
        self.cmd.append(NETWORK_YML)

    def test_show(self):
        self.cmd.append("show")
        res = executeAndReturnOutput(self.cmd)
        self.assertEqual(res[0], 0)
        self.assertRegex(res[1], "lb01 => 172.30.52.135:5060")
        self.assertRegex(res[1], "prx01 => 172.30.52.132:5062")

    def test_delete_resolve_no_format(self):
        self.cmd.append("delete")
        self.cmd.append("lb01")
        self.cmd.append("test@domain.ok")
        res = executeAndReturnOutput(self.cmd)
        self.assertEqual(res[0], 0)
        self.assertRegex(
            res[1], r"lb01\[lb\] resolved to:172\.30\.52\.135:5060"
        )
        self.assertRegex(res[1], r"lb01\[redis\] resolved to:172\.30\.52\.173")
        self.assertRegex(
            res[1], "Client: would connect to http://172.30.52.135:5060/"
        )

    def test_list_resolve_no_format(self):
        self.cmd.append("list")
        self.cmd.append("lb01")
        res = executeAndReturnOutput(self.cmd)
        self.assertEqual(res[0], 0)
        self.assertRegex(
            res[1], r"lb01\[lb\] resolved to:172\.30\.52\.135:5060"
        )
        self.assertRegex(
            res[1], "Client: would connect to http://172.30.52.135:5060/"
        )

    def test_add_resolve(self):
        self.cmd.append("add")
        self.cmd.append("lb01")
        self.cmd.append("test@domain.ok")
        self.cmd.append("prx01")
        res = executeAndReturnOutput(self.cmd)
        self.assertEqual(res[0], 0)
        self.assertRegex(
            res[1], r"lb01\[lb\] resolved to:172\.30\.52\.135:5060"
        )
        self.assertRegex(res[1], r"lb01\[redis\] resolved to:172\.30\.52\.173")
        self.assertRegex(
            res[1],
            r"prx01\[proxy\] resolved to:sip:172\.30\.52\.132:5062",
        )
        self.assertRegex(
            res[1], "Client: would connect to http://172.30.52.135:5060/"
        )
        self.assertRegex(res[1], r"htable\.sets.+sip:172\.30\.52\.132\:5062")
