import codecs
import ntpath
from xml.etree import ElementTree

from parallels.core import messages
from parallels.core.logging import get_logger

logger = get_logger(__name__)


class WebConfigFile(object):
    # ElementTree works with namespaces in a way which breaks web.config file:
    # when reading and then saving the same file with namespaces, it could move/rename
    # namespaces, while IIS expects namespace attributes at specific place.
    #
    # Test script to show that:
    # =================================================================
    # from xml.etree import ElementTree
    # contents = """
    # <a>
    #     <b xmlns="x"/>
    # </a>
    # """
    # root_elem = ElementTree.fromstring(contents)
    # print ElementTree.tostring(root_elem, 'utf-8', 'xml')
    # =================================================================
    #
    # As result we see moved "xmlns" tag and new "ns0" instance:
    # =================================================================
    # <a xmlns:ns0="x">
    #    <ns0:b />
    # </a>
    # =================================================================
    #
    # To avoid that, we temporary rename all namespace attributes to some fake name,
    # and once the contents is modified by ElementTree, then rename attributes back.
    # Renaming is performed by simple string replacement, so theoretically there could be cases
    # when it does not work/break XML. But in practice, it should be very rare.
    NAMESPACE_ORIG_STR = "xmlns="
    NAMESPACE_RENAMED_STR = "pleskmigratorxmlns="

    def __init__(self, subscription, filename, xml):
        """
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        :type filename: str | unicode
        :type xml: xml.etree.ElementTree.Element
        """
        self._filename = filename
        self._xml = xml
        self._subscription = subscription

    @property
    def xml(self):
        """Get XML object of web.config.file

        :rtype xml.etree.ElementTree.Element
        """
        return self._xml

    @property
    def filename(self):
        """Get full file name of web.config file

        :rtype: str | unicode
        """
        return self._filename

    def save(self):
        """Save web.config file from XML object

        :rtype: None
        """
        new_contents = ElementTree.tostring(self._xml, 'utf-8', 'xml')
        new_contents = new_contents.replace(self.NAMESPACE_RENAMED_STR, self.NAMESPACE_ORIG_STR)
        with self._subscription.web_target_server.runner() as runner:
            runner.upload_file_content(self._filename, new_contents)

    @classmethod
    def iter_for_domain(cls, subscription, domain):
        """
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        :type domain: parallels.core.dump.data_model.Domain
        """
        www_root = ntpath.join(
            subscription.web_target_server.vhosts_dir, subscription.name_idn, domain.www_root
        )
        with subscription.web_target_server.runner() as runner:
            for filename in runner.iter_files_list_nested(www_root):
                if not filename.lower().endswith('web.config'):
                    continue

                if runner.get_file_size(filename) > 1024 * 1024:
                    # Skip large files that are > 1MB, to avoid high memory usage.
                    # web.config files should be always small.
                    continue

                contents = runner.get_file_contents(filename)

                utf8_bom_mark = codecs.BOM_UTF8
                if contents.startswith(utf8_bom_mark):
                    contents = contents[len(utf8_bom_mark):]

                contents = contents.replace(cls.NAMESPACE_ORIG_STR, cls.NAMESPACE_RENAMED_STR)

                # Catch all exceptions to log name of the file for which parsing has failed
                # and continue to the next file.
                # noinspection PyBroadException
                try:
                    root_elem = ElementTree.fromstring(contents)
                except Exception:
                    logger.exception()
                    logger.fdebug(messages.DEBUG_SKIP_INVALID_XML_WEB_CONFIG, filename=filename)
                    continue

                yield cls(subscription=subscription, xml=root_elem, filename=filename)
