from __future__ import absolute_import

import sys
from copy import deepcopy
from xml.dom import minidom
from xml.etree import ElementTree

from parallels.core import messages
from parallels.core.logging import get_logger
from parallels.core.utils.common import default
from plesk_migrator.compatibility.utils import stringio

logger = get_logger(__name__)


def seq(*items):
    return [item for item in items if item is not None]


def seq_iter(items):
    return seq(*items)


def elem(name, children=None, attributes=None):
    if children is None:
        children = []
    if attributes is None:
        attributes = {}
    n = ElementTree.Element(name, attributes)
    n.extend(children)
    return n


def list_elem(name, children, attributes=None):
    """
    :type name: str|unicode
    :type children: list[xml.etree.ElementTree.Element] | None
    :type attributes: dict[str|unicode, str|unicode] | None
    :rtype: xml.etree.ElementTree.Element | None
    """
    if not children:
        return None
    return elem(name, children, attributes)


def text_elem(name, value, attributes=None):
    if sys.version_info.major >= 3:
        str_type = str
    else:
        str_type = unicode
    if value is None:
        return None
    if attributes is None:
        attributes = {}
    el = ElementTree.Element(name, attributes)
    el.text = str_type(value)
    return el


def attrs(items):
    """Returns only items with value != None

    :type items: dict | None
    :rtype: dict | None
    """
    if items is None:
        return None
    return {
        name: value
        for name, value in items.items() if value is not None
    }


def xml_bool(value):
    if value is None:
        return None
    return 'true' if value else 'false'


def bool_elem(name, value):
    return text_elem(name, xml_bool(value))


def create_xml_stream(xml, xml_declaration=True):
    stream = stringio(is_unicode=True)
    xml.write(stream, xml_declaration=xml_declaration, encoding='utf-8')
    stream.seek(0)
    return stream


def replace_root_node_if_name_matches(source_xml, root_tag_name, new_root_node):
    if source_xml.tag == root_tag_name:
        target_xml = deepcopy(new_root_node)
        for child in source_xml:
            target_xml.append(deepcopy(child))
        return target_xml
    else:
        return deepcopy(source_xml)


def multiple_findall(node, paths):
    for path in paths:
        for found in node.findall(path):
            yield found


def multiple_find(node, paths):
    for path in paths:
        found = node.find(path)
        if found is not None:
            return found
    return None


def multiple_attrib_get(node, attrib_names, default_value=None):
    for attrib_name in attrib_names:
        if attrib_name in node.attrib:
            return node.attrib[attrib_name]
    return default_value


def prettify_xml(xml_string):
    """Prettify XML with indentation

    :type xml_string: str | unicode
    :rtype: str | unicode
    """
    xml = minidom.parseString(xml_string)
    return xml.toprettyxml(indent='  ', encoding='utf-8')


def xml_to_string_pretty(tree):
    """Serialize XML into a string, with pretty indentation

    :type tree: xml.etree.ElementTree.ElementTree | xml.etree.ElementTree.Element
    :rtype: str | unicode
    """
    if isinstance(tree, ElementTree.ElementTree):
        root_element = tree.getroot()
    else:
        root_element = tree

    remove_empty_items(tree)
    return prettify_xml(
        ElementTree.tostring(root_element, 'utf-8', 'xml')
    )


def remove_empty_items(tree):
    """Cleanup XML tree by removing empty (None) attributes and None child nodes.

    If you have a None attribute or child node and tried to serialize tree with ElementTree.tostring you will
    get exception with message stack trace which is quite difficult to investigate.
    If you call this function before serializing:
    1) Such attributes/nodes will be removed - so serialization won't fail.
    2) You will get a warning to migrator logs (only to logs, not to reports, to avoid bothering customer)
    with XML node (attribute name) where the None attribute/node occurred.

    :type tree: xml.etree.ElementTree.ElementTree | xml.etree.ElementTree.Element
    """
    if isinstance(tree, ElementTree.ElementTree):
        root_element = tree.getroot()
    else:
        root_element = tree

    _remove_empty_items(root_element)


def _remove_empty_items(element, path=None):
    """
    :type element: xml.etree.ElementTree.Element
    """
    path = default(path, [])

    attribs_to_remove = []

    for attr_name, attr_value in element.attrib.items():
        if attr_value is None:
            logger.fwarn(messages.SERIALIZE_XML_NONE_ATTRIBUTE, attr_name=attr_name, xml_node="/".join(path))
            attribs_to_remove.append(attr_name)

    for attrib_to_remove in attribs_to_remove:
        del element.attrib[attrib_to_remove]

    children_to_remove = []

    for child in element:
        if child is None:
            logger.fwarn(messages.SERIALIZE_XML_NONE_NODE, tag=element.tag, xml_nodes="/".join(path))
            children_to_remove.append(child)
        else:
            _remove_empty_items(child, path + [element.tag])

    for child_to_remove in children_to_remove:
        element.remove(child_to_remove)
