import abc
import collections
import functools
import importlib
import json
import sys
import unittest
from typing import Dict
from unittest.mock import patch

from library_test.mock_manage.mock_manager import MockManager
from library_test.mock_manage.mock_model.testcase_model import TestCase

REPLACE_DICT = collections.defaultdict(lambda: False)


def replace_module_tag(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        res = None
        if not REPLACE_DICT.get(func.__name__):
            res = func(*args, **kwargs)
            REPLACE_DICT[func.__name__] = True
        return res

    return wrapper


class BaseTest(unittest.TestCase):

    def setUp(self) -> None:
        super().setUp()

    @classmethod
    def replace_module(cls, source_path, target_path):
        replacement_module = importlib.import_module(target_path)
        sys.modules[source_path] = replacement_module

    @classmethod
    @replace_module_tag
    def replace_ansible_module(cls):
        cls.replace_module("ansible.module_utils", "ascend_deployer.module_utils")
        cls.replace_module("ansible.module_utils.basic", "library_test.mock_manage.mock_model.mock_ansible_module")
        cls.replace_module("ansible.module_utils.urls", "library_test.mock_manage.mock_model.mock_urls")
        cls.replace_module("ansible.module_utils._text", "library_test.mock_manage.mock_model.mock_text")

    @classmethod
    @replace_module_tag
    def replace_linux_module(cls):
        cls.replace_module("grp", "library_test.mock_manage.mock_model.mock_grp")
        cls.replace_module("pwd", "library_test.mock_manage.mock_model.mock_pwd")

    @classmethod
    @replace_module_tag
    def replace_dl(cls):
        cls.replace_module("ansible.module_utils.dl", "ascend_deployer.module_utils.dl")

    @classmethod
    def setUpClass(cls) -> None:
        super().setUpClass()
        # 替换顺序非常重要!! 请勿随意修改!!
        cls.replace_ansible_module()


class BaseLibraryTest(BaseTest):

    def setUp(self) -> None:
        super().setUp()

    @classmethod
    def setUpClass(cls) -> None:
        super().setUpClass()
        test_case = TestCase(cls.get_testcase_path())
        cls.mock_manager = MockManager(test_case)
        cls.mock_manager.mock_file_handler.start_mock()  # 位置很关键,file_handler必须先执行
        cls.mock_manager.mock_file_handler.add_file(test_case.file_config)
        cls.mock_manager.start_subprocess_patcher("ascend_deployer.module_utils.common_info")
        cls.mock_manager.start_ansible_module_patcher(cls.get_module_path())
        cls.mock_manager.start_uid_patcher(cls.get_module_path())
        cls.mock_manager.start_gid_patcher(cls.get_module_path())
        cls.mock_manager.start_arch_patcher(cls.get_module_path())
        cls.mock_manager.start_tar_open_patcher(cls.get_module_path())
        cls.mock_manager.start_zip_open_patcher(cls.get_module_path())
        cls.mock_manager.start_env_patcher(cls.get_module_path())

    @classmethod
    def parse_context_exception(cls, context) -> Dict:
        return json.loads(str(context.exception))

    @classmethod
    @abc.abstractmethod
    def get_testcase_path(cls):
        pass

    @classmethod
    @abc.abstractmethod
    def get_module_path(cls):
        pass

    @classmethod
    def tearDownClass(cls) -> None:
        super().tearDownClass()
        cls.mock_manager.mock_file_handler.end_mock()
        patch.stopall()

    def tearDown(self) -> None:
        patch.stopall()