import random
import string
from contextlib import contextmanager

from parallels.core import messages
from parallels.core.logging import get_logger
from parallels.core.runners.base import BaseRunner
from parallels.core.utils.common import is_empty
from parallels.core.utils.migrator_utils import normalize_domain_name
from parallels.core.utils.paths.converters.windows.source import WindowsSourceWebPathConverter
from parallels.core.utils.paths.converters.windows.target import WindowsTargetWebPathConverter
from parallels.core.utils.paths.copy_web_content import CopyWebContentItem
from parallels.core.utils.paths.web_paths import SiteDocumentRoot

logger = get_logger(__name__)


def list_domains_to_detect_scripting(global_context, subscription):
    """Iterate over domains for which it makes sense to detect scripting settings

    Returns list of tuples, each tuple has 3 elements:
    - domain object
    - source document root (string)
    - source domain IP (string)

    :type global_context: parallels.core.global_context.GlobalMigrationContext
    :type subscription: parallels.core.migrated_subscription.MigratedSubscription
    :rtype: tuple
    """
    domains_to_detect_scripting = []

    for domain in subscription.converted_dump.iter_domains():
        if subscription.is_fake and normalize_domain_name(subscription.name) == normalize_domain_name(domain.name):
            continue

        if not domain.is_virtual_hosting:
            continue

        source_document_root = _detect_source_document_root(global_context, subscription, domain)
        if is_empty(source_document_root):
            continue

        with subscription.web_source_server.runner() as runner:
            if not runner.file_exists(source_document_root):
                continue

        source_domain_ip = _get_domain_source_ip(subscription, domain)
        if is_empty(source_domain_ip):
            continue

        domains_to_detect_scripting.append(
            (domain, source_document_root, source_domain_ip)
        )

    return domains_to_detect_scripting


def _get_domain_source_ip(subscription, domain):
    """Get IP address where web service works on the source server for specified domain

    :type subscription: parallels.core.migrated_subscription.MigratedSubscription
    :type domain: parallels.core.dump.data_model.Domain
    :rtype: str | unicode | None
    """
    raw_subscription = subscription.raw_dump
    raw_domain = subscription.raw_dump.get_domain(domain.name)

    return (
        raw_domain.web_ips.v4 or raw_domain.web_ips.v6 or
        raw_subscription.ip or raw_subscription.ipv6
    )


def _detect_source_document_root(global_context, subscription, domain):
    """Detect where document root is located on the source server according to list of copied web directories

    :type global_context: parallels.core.global_context.GlobalMigrationContext
    :type subscription: parallels.core.migrated_subscription.MigratedSubscription
    :type domain: parallels.core.dump.data_model.Domain
    :rtype: str | unicode | None
    """
    target_path_converter = WindowsTargetWebPathConverter()

    source_vhosts_dir = global_context.migrator.web_files.get_source_vhosts_dir(
        global_context, subscription.web_source_server
    )
    source_path_converter = WindowsSourceWebPathConverter(vhost_dir=source_vhosts_dir)

    target_document_root = target_path_converter.expand(
        SiteDocumentRoot(subscription.converted_dump, domain),
        subscription.web_target_server
    )

    files_to_copy = global_context.migrator.web_files.list_files_to_copy(global_context, subscription)

    outer_source_directory = None
    outer_target_directory = None

    for file_item in files_to_copy:
        assert isinstance(file_item, CopyWebContentItem)

        target_directory = target_path_converter.expand(
            file_item.target_path, subscription.web_target_server
        )
        if (
            target_document_root.startswith(target_directory) and
            (
                outer_target_directory is None or
                len(outer_target_directory) < len(target_directory)
            )
        ):
            outer_target_directory = target_directory
            outer_source_directory = source_path_converter.expand(
                file_item.source_path, subscription.web_source_server
            )

    if outer_target_directory is not None:
        suffix = target_document_root[len(outer_target_directory):]
        source_document_root = outer_source_directory + suffix
        logger.fdebug(
            messages.DETECTED_SOURCE_DOCUMENT_ROOT, domain=domain.name, source_document_root=source_document_root
        )
        return source_document_root
    else:
        logger.fdebug(messages.SOURCE_DOCUMENT_ROOT_NOT_FOUND, domain=domain.name)
        return None


@contextmanager
def upload_probe(server, directory, script_extension, script_contents, script_random_suffix_length=16):
    """Context manager to upload probe file (e.g. PHP or ASP script) to the server and remove it on exit from context

    :type server: parallels.core.connections.server.Server
    :type directory: str | unicode
    :type script_extension: str | unicode
    :type script_contents: str | unicode
    :type script_random_suffix_length: int
    """
    with server.runner() as runner:
        assert isinstance(runner, BaseRunner)

        random_suffix = "".join(
            random.choice(string.digits + string.ascii_lowercase) for _ in range(script_random_suffix_length)
        )
        temp_script_name = "plesk-migrator-{random_suffix}.{script_extension}".format(
            random_suffix=random_suffix, script_extension=script_extension
        )
        temp_script_path = server.join_file_path(directory, temp_script_name)

        runner.upload_file_content(temp_script_path, script_contents)
        try:
            yield temp_script_name
        finally:
            runner.remove_file(temp_script_path)
