#  Licensed under the Apache License, Version 2.0 (the "License"); you may
#  not use this file except in compliance with the License. You may obtain
#  a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#  License for the specific language governing permissions and limitations
#  under the License.

import multiprocessing
import random
import time

from unittest import mock

from gbpservice.nfp.core import context as nfp_context
from gbpservice.nfp.core import controller as nfp_controller
from gbpservice.nfp.core import event as nfp_event
from gbpservice.nfp.core import log as nfp_logging
from gbpservice.nfp.core import manager as nfp_manager
from gbpservice.nfp.core import worker as nfp_worker
from oslo_config import cfg as oslo_config
import six
import unittest2

NFP_MODULES_PATH = ['gbpservice.neutron.tests.unit.nfp.core']


def mocked_get_logging_context(**kwargs):
    return {
        'meta_id': '',
        'auth_token': None,
        'namespace': None}


nfp_logging.get_logging_context = mocked_get_logging_context


class MockedPipe(object):

    def __init__(self):
        self.fd = random.randint(14, 34)
        self.other_end_event_proc_func = None

    def poll(self, *args, **kwargs):
        return False

    def send(self, event):
        self.other_end_event_proc_func(event)


class MockedProcess(object):

    def __init__(self, parent_pipe=None, child_pipe=None,
                 controller=None):

        self.parent_pipe = parent_pipe
        self.child_pipe = child_pipe
        self.controller = controller
        self.daemon = True
        self.pid = random.randint(8888, 9999)

    def start(self):
        self.worker = nfp_worker.NfpWorker({}, threads=0)
        self.worker.parent_pipe = self.parent_pipe
        self.worker.pipe = self.child_pipe
        self.worker.controller = nfp_controller.NfpController(
            self.controller._conf, singleton=False)

        # fork a new controller object
        self.worker.controller.PROCESS_TYPE = "worker"
        self.worker.controller._pipe = self.worker.pipe
        self.worker.controller._event_handlers = (
            self.controller._event_handlers)
        self.worker.event_handlers = self.controller.get_event_handlers()

        self.parent_pipe.other_end_event_proc_func = (
            self.worker._process_event)
        self.child_pipe.other_end_event_proc_func = (
            self.controller._process_event)


def mocked_pipe(**kwargs):
    return MockedPipe(), MockedPipe()


def mocked_process(target=None, args=None):
    return MockedProcess(parent_pipe=args[1],
                         child_pipe=args[2],
                         controller=args[3])


nfp_controller.PIPE = mocked_pipe
nfp_controller.PROCESS = mocked_process


class MockedWatchdog(object):

    def __init__(self, handler, seconds=1, event=None):
        if event and event.desc.type == 'poll_event':
            # time.sleep(seconds)
            handler(event=event)

    def cancel(self):
        pass


nfp_manager.WATCHDOG = MockedWatchdog


class Object(object):

    def __init__(self):
        pass


class Test_Process_Model(unittest2.TestCase):

    def setUp(self):
        nfp_context.init()

    def _mocked_fork(self, args):
        proc = Object()
        pid = random.randint(8888, 9999)
        setattr(proc, 'pid', pid)
        return proc

    def _mocked_oslo_wrap(self):
        wrap = Object()
        setattr(wrap, 'service', {})
        return wrap

    def _mocked_event_ack(self, event):
        if event.id == 'TEST_EVENT_ACK_FROM_WORKER':
            if hasattr(event, 'desc'):
                if event.desc.worker:
                    self.controller.event_ack_wait_obj.set()

    def test_nfp_module_init(self):
        conf = oslo_config.CONF
        conf.nfp_modules_path = NFP_MODULES_PATH
        controller = nfp_controller.NfpController(conf, singleton=False)
        wait_obj = multiprocessing.Event()
        setattr(controller, 'nfp_module_init_wait_obj', wait_obj)
        nfp_controller.load_nfp_modules(conf, controller)
        controller.nfp_module_init_wait_obj.wait(1)
        called = controller.nfp_module_init_wait_obj.is_set()
        self.assertTrue(called)

    def test_nfp_module_init_wrong_path(self):
        conf = oslo_config.CONF
        conf.nfp_modules_path = ['tmp.nfp']
        controller = nfp_controller.NfpController(oslo_config.CONF,
                                                  singleton=False)
        wait_obj = multiprocessing.Event()
        setattr(controller, 'nfp_module_init_wait_obj', wait_obj)
        nfp_controller.load_nfp_modules(conf, controller)
        controller.nfp_module_init_wait_obj.wait(1)
        called = controller.nfp_module_init_wait_obj.is_set()
        self.assertFalse(called)

    def test_nfp_module_post_init_called(self):
        conf = oslo_config.CONF
        conf.nfp_modules_path = NFP_MODULES_PATH
        controller = nfp_controller.NfpController(conf, singleton=False)
        wait_obj = multiprocessing.Event()
        setattr(controller, 'nfp_module_post_init_wait_obj', wait_obj)
        nfp_modules = nfp_controller.load_nfp_modules(conf, controller)
        nfp_controller.nfp_modules_post_init(conf, nfp_modules, controller)
        controller.nfp_module_post_init_wait_obj.wait(1)
        called = controller.nfp_module_post_init_wait_obj.is_set()
        self.assertTrue(called)

    def test_nfp_module_post_init_ignored(self):
        # None the post_init method in test handler
        from gbpservice.neutron.tests.unit.nfp.core import (
            nfp_module)
        del nfp_module.nfp_module_post_init

        conf = oslo_config.CONF
        conf.nfp_modules_path = NFP_MODULES_PATH
        controller = nfp_controller.NfpController(conf, singleton=False)
        wait_obj = multiprocessing.Event()
        setattr(controller, 'nfp_module_post_init_wait_obj', wait_obj)
        nfp_modules = nfp_controller.load_nfp_modules(conf, controller)
        nfp_controller.nfp_modules_post_init(conf, nfp_modules, controller)
        controller.nfp_module_post_init_wait_obj.wait(1)
        called = controller.nfp_module_post_init_wait_obj.is_set()
        self.assertFalse(called)

    @mock.patch(
        'gbpservice.nfp.core.controller.NfpController._fork'
    )
    def test_nfp_controller_launch_2_workers(self, mock_fork):
        mock_fork.side_effect = self._mocked_fork
        conf = oslo_config.CONF
        conf.nfp_modules_path = []
        controller = nfp_controller.NfpController(conf, singleton=False)
        controller.launch(2)
        # Check if 2 workers are created
        workers = controller.get_childrens()
        pids = list(workers.keys())
        self.assertEqual(len(pids), 2)
        self.assertTrue(pid in range(8888, 9999) for pid in pids)

    @mock.patch(
        'gbpservice.nfp.core.controller.NfpController._fork'
    )
    def test_nfp_controller_launch_4_workers(self, mock_fork):
        mock_fork.side_effect = self._mocked_fork
        conf = oslo_config.CONF
        conf.nfp_modules_path = []
        controller = nfp_controller.NfpController(conf, singleton=False)
        controller.launch(4)
        # Check if 4 workers are created
        workers = controller.get_childrens()
        pids = list(workers.keys())
        self.assertEqual(len(pids), 4)
        self.assertTrue(pid in range(8888, 9999) for pid in pids)

    @mock.patch(
        'gbpservice.nfp.core.controller.NfpController._fork'
    )
    def test_nfp_rsrc_manager_new_childs(self, mock_fork):
        mock_fork.side_effect = self._mocked_fork
        conf = oslo_config.CONF
        conf.nfp_modules_path = []
        controller = nfp_controller.NfpController(conf, singleton=False)
        controller.launch(2)
        controller._update_manager()
        # Check if 2 workers are added to manager
        pids = list(controller._manager._resource_map.keys())