import os
from xml.etree import ElementTree

from parallels.core import messages
from parallels.core.logging import get_logger
from parallels.core.registry import Registry
from parallels.core.utils.common import default, is_empty
from parallels.core.utils.common_constants import (
    MIGRATOR_EXTENSION_XML, ADDITIONAL_SOURCES_DIR, THIRDPARTY_ADDITIONAL_SOURCES_DIR
)
from parallels.core.utils.entity import Entity
from parallels.plesk.hosting_description.input_formats import formats

logger = get_logger(__name__)


class TargetPanels(object):
    PLESK = 'plesk'
    PLESK_MULTI_SERVER = 'plesk_multi_server'


class AdditionalSourcePanelConfig(Entity):
    EXTENSION_ENTRY_POINT_EXECUTABLE = 'executable'
    EXTENSION_ENTRY_POINT_MIGRATOR_PYTHON = 'migrator-python'

    def __init__(self, source_panel_id, extension_dir, show_in_gui, version, entry_point, execution,
                 hosting_description_filename, hosting_description_format):
        self._source_panel_id = source_panel_id
        self._extension_dir = extension_dir
        self._show_in_gui = show_in_gui
        self._version = version
        self._entry_point = entry_point
        self._execution = execution
        self._hosting_description_filename = hosting_description_filename
        self._hosting_description_format = hosting_description_format

    @property
    def source_panel_id(self):
        """Path to extension dir.
        :rtype: str
        """
        return self._source_panel_id

    @property
    def extension_dir(self):
        """Path to extension dir.
        :rtype: str
        """
        return self._extension_dir

    @property
    def show_in_gui(self):
        """Whether to show this extension in migrator GUI.
        For extensions that require complex configuration that could not be handled by GUI,
        this option should be set to false.
        :rtype: bool
        """
        return self._show_in_gui

    @property
    def version(self):
        """Version of the extension
        :rtype: str
        """
        return self._version

    @property
    def entry_point(self):
        """Field describing which file should be started to create hosting description.
        :rtype: str
        """
        return self._entry_point

    @property
    def execution(self):
        """How entry point should be executed.
        :rtype: str
        """
        return self._execution

    @property
    def hosting_description_filename(self):
        """Path to hosting description file.
        :rtype: str
        """
        return self._hosting_description_filename

    @property
    def hosting_description_format(self):
        """Format of hosting description file.
        :rtype: str
        """
        return self._hosting_description_format


def list_target_panels():
    return list(_iter_target_panels())


def list_built_in_source_panels(target_panel):
    return list(_iter_built_in_source_panels(target_panel))


def list_additional_source_panels():
    return list(get_additional_source_panels().keys())


def list_all_source_panels(target_panel):
    return list_built_in_source_panels(target_panel) + list_additional_source_panels()


def load_target_panel_class(target_panel, module_name, class_name, optional=False):
    if optional:
        root_modules_dir = os.path.join(os.path.dirname(__file__), '..')
        if not os.path.exists(os.path.join(root_modules_dir, target_panel, '%s.py' % module_name)):
            # if module is optional and it does not exist - return None instead of raising import exception
            return None

    module = __import__(
        'parallels.%s.%s' % (target_panel, module_name),
        globals(), locals(), [class_name]
    )
    return getattr(module, class_name)


def load_base_source_panel_class():
    module = __import__('parallels.core.migrator', globals(), locals(), ['Migrator'])
    return getattr(module, 'Migrator')


def load_source_panel_class(source_panel, module_name, class_name, optional=False):
    target_panel = TargetPanels.PLESK  # all source panels are stored inside Plesk target panel

    source_extension_panels = list_additional_source_panels()
    # if source panel is not in list of built in source panels - use 'custom' base
    if source_panel in source_extension_panels:
        source_panel = 'custom'

    if optional:
        root_modules_dir = os.path.join(os.path.dirname(__file__), '..')
        module_path = os.path.join(root_modules_dir, target_panel, 'source', source_panel, '%s.py' % module_name)
        if not os.path.exists(module_path):
            # if module is optional and it does not exist - return None instead of raising import exception
            return None
    module = __import__(
        'parallels.%s.source.%s.%s' % (target_panel, source_panel, module_name),
        globals(), locals(), [class_name]
    )
    if optional and not hasattr(module, class_name):
        return None
    return getattr(module, class_name)


def is_additional_source_panel(global_context):
    """ Check if source panel is additional source panel

    :type global_context: parallels.core.global_context.GlobalMigrationContext
    :rtype: bool
    """
    return get_additional_source_panel(global_context) is not None


def get_additional_source_panel(global_context):
    """
    :type global_context: parallels.core.global_context.GlobalMigrationContext
    :rtype: parallels.core.panels.AdditionalSourcePanelConfig | None
    """
    source_type = global_context.config.get('GLOBAL', 'source-type')
    if source_type in list_additional_source_panels():
        return get_additional_source_panels()[source_type]
    else:
        return None


def get_additional_source_panels():
    """
    :rtype: dict[str | unicode, parallels.core.panels.AdditionalSourcePanelConfig]
    """
    configs = []

    base_dir = Registry.get_instance().get_base_dir()
    var_dir = Registry.get_instance().get_var_dir()

    if not is_empty(base_dir):
        ext_dir = os.path.join(base_dir, ADDITIONAL_SOURCES_DIR)

        if os.path.isdir(ext_dir):
            ext_paths = [os.path.join(ext_dir, d) for d in os.listdir(ext_dir)]
        else:
            ext_paths = []
    else:
        ext_paths = []

    if not is_empty(var_dir):
        thirdparty_ext_dir = os.path.join(var_dir, THIRDPARTY_ADDITIONAL_SOURCES_DIR)

        if os.path.isdir(thirdparty_ext_dir):
            thirdparty_ext_paths = [os.path.join(thirdparty_ext_dir, d) for d in os.listdir(thirdparty_ext_dir)]
        else:
            thirdparty_ext_paths = []
    else:
        thirdparty_ext_paths = []

    for plesk_extension_dir in ext_paths + thirdparty_ext_paths:
        if os.path.isdir(plesk_extension_dir):
            extension_xml = os.path.join(plesk_extension_dir, MIGRATOR_EXTENSION_XML)
            if os.path.exists(extension_xml):
                try:
                    tree = ElementTree.parse(extension_xml)
                    source_panel_id = os.path.basename(plesk_extension_dir)
                    if source_panel_id is not None:
                        show_in_gui = default(tree.findtext('show-in-gui'), 'false').lower() == 'true'
                        version = default(tree.findtext('version'), '1.0')
                        entry_point = default(tree.findtext('entry-point'), 'create-hosting-description')
                        execution = tree.findtext('execution')
                        supported_entry_points = [
                            AdditionalSourcePanelConfig.EXTENSION_ENTRY_POINT_EXECUTABLE,
                            AdditionalSourcePanelConfig.EXTENSION_ENTRY_POINT_MIGRATOR_PYTHON
                        ]
                        if execution not in supported_entry_points:
                            logger.fwarn(
                                messages.INVALID_EXTENSION_ENTRY_POINT,
                                entry_point=execution,
                                extension_name=source_panel_id,
                                supported_entry_points=supported_entry_points
                            )
                            execution = AdditionalSourcePanelConfig.EXTENSION_ENTRY_POINT_EXECUTABLE
                        hosting_description = default(tree.findtext('hosting-description'), 'hosting-description.yaml')
                        hosting_description_name, hosting_description_extension = os.path.splitext(hosting_description)
                        hosting_description_extension = hosting_description_extension.lstrip('.')
                        input_formats = [input_format.name for input_format in formats.INPUT_FORMATS]
                        hosting_description_format = tree.findtext('hosting-description-format')
                        if hosting_description_format not in input_formats:
                            if hosting_description_extension in input_formats:
                                hosting_description_format = hosting_description_extension
                            else:
                                hosting_description_format = 'yaml'
                        config = AdditionalSourcePanelConfig(
                            source_panel_id=source_panel_id,
                            extension_dir=plesk_extension_dir,
                            show_in_gui=show_in_gui,
                            version=version,
                            entry_point=entry_point,
                            execution=execution,
                            hosting_description_filename=hosting_description,
                            hosting_description_format=hosting_description_format
                        )
                        configs.append(config)
                except Exception as e:
                    logger.exception()
                    logger.fwarn(messages.FAILED_TO_PARSE_EXTENSION_XML, reason=e)
    return {config.source_panel_id: config for config in configs}


def _iter_target_panels():
    root_modules_dir = os.path.join(os.path.dirname(__file__), '..')
    for module_dir in os.listdir(root_modules_dir):
        if os.path.isdir(os.path.join(root_modules_dir, module_dir)):
            if os.path.exists(os.path.join(root_modules_dir, module_dir, 'workflow.py')):
                yield module_dir


def _iter_built_in_source_panels(target_panel):
    root_modules_dir = os.path.join(os.path.dirname(__file__), '..')
    target_panel_dir = os.path.join(root_modules_dir, target_panel)
    source_panels_dir = os.path.join(target_panel_dir, 'source')

    if not os.path.exists(source_panels_dir):
        return

    for module_dir in os.listdir(source_panels_dir):
        if os.path.exists(os.path.join(source_panels_dir, module_dir, 'workflow.py')):
            yield module_dir
