import itertools

from collections import namedtuple

from parallels.plesk.utils.xml_rpc.plesk.operator.dns import DnsOperator, Soa
from parallels.plesk.utils.xml_rpc.plesk.operator.dns9 import DnsOperator9
from parallels.plesk.utils.xml_rpc.plesk.operator.subdomain import SubdomainOperator
from parallels.plesk.utils.xml_rpc.plesk.operator.alias import AliasOperator
from parallels.plesk.utils.xml_rpc.plesk.operator.domain_alias import DomainAliasOperator
from parallels.plesk.utils.xml_rpc.plesk.operator.site import SiteOperator
from parallels.plesk.utils.xml_rpc.plesk.operator.subscription import SubscriptionOperator
from parallels.plesk.utils.xml_rpc.plesk.operator.domain import DomainOperator
from parallels.core import version_tuple
from parallels.core.utils.common import split_list_by_count
from parallels.core.utils.yaml_utils import pretty_yaml

DnsTimings = pretty_yaml(namedtuple('DnsTimings', ('sites', 'aliases')))
SOA = pretty_yaml(namedtuple('SOA', ('ttl', 'refresh', 'retry', 'expire', 'minimum')))

# On large source installations (with more than 500 domains) Plesk fails to perform some queries
# if filter is too large because of Plesk PHP memory limits. 
# So we split every large query into several small ones so filter length is
# less or equal to MAX_PLESK_API_FILTER_LENGTH.
MAX_PLESK_API_FILTER_LENGTH = 128


def set_low_dns_timings(plesk_api, backup, ttl_value):
    low_timing_soa = Soa(
        ttl=ttl_value,
        refresh=ttl_value,
        retry=ttl_value,
        expire=None,  # Do not change.
        minimum=ttl_value,
    )
        
    plesk_api_helper = _get_plesk_api_helper(plesk_api)

    if version_tuple(plesk_api.api_version) <= (1, 6, 0, 2):
        _execute_plesk_api_splitted_no_result(
            lambda domain_ids: plesk_api_helper.set_soa_for_domains(domain_ids, low_timing_soa),
            [id for id, _ in _get_all_domains(plesk_api, backup)]
        )
    else:
        _execute_plesk_api_splitted_no_result(
            lambda site_ids: plesk_api_helper.set_soa_for_sites(site_ids, low_timing_soa),
            [id for id, _ in _get_all_sites(backup, plesk_api)]
        )

    _execute_plesk_api_splitted_no_result(
        lambda alias_ids: plesk_api_helper.set_soa_for_aliases(alias_ids, low_timing_soa),
        [id for id, _ in _get_all_aliases(plesk_api, backup)]
    )


def get_dns_timings(plesk_api, backup):
    if version_tuple(plesk_api.api_version) <= (1, 6, 0, 2):
        return DnsTimings(
            sites=_get_domains_dns_timings(plesk_api, backup),
            aliases=_get_aliases_dns_timings(plesk_api, backup)
        )
    else:
        return DnsTimings(
            sites=_get_sites_dns_timings(plesk_api, backup),
            aliases=_get_aliases_dns_timings(plesk_api, backup)
        )


def _get_sites_dns_timings(plesk_api, backup):
    all_sites = _get_all_sites(backup, plesk_api)
    site_soa = _execute_plesk_api_splitted(Plesk10ApiHelper(plesk_api).get_soa_for_sites, [id for id, _ in all_sites])
    site_names_by_id = dict(all_sites)
    return {site_names_by_id[site_id]: _pretty_yaml_soa(soa) for site_id, soa in site_soa}


def _get_domains_dns_timings(plesk_api, backup):
    all_domains = _get_all_domains(plesk_api, backup)
    domain_soa = _execute_plesk_api_splitted(Plesk9ApiHelper(plesk_api).get_soa_for_domains, [id for id, _ in all_domains])
    domain_names_by_id = dict(all_domains)
    return {domain_names_by_id[domain_id]: _pretty_yaml_soa(soa) for domain_id, soa in domain_soa}


def _get_aliases_dns_timings(plesk_api, backup):
    all_aliases = _get_all_aliases(plesk_api, backup)
    alias_names_by_id = dict(all_aliases)
    plesk_api_helper = _get_plesk_api_helper(plesk_api)
    alias_soa = _execute_plesk_api_splitted(plesk_api_helper.get_soa_for_aliases, [id for id, _ in all_aliases])
    return {alias_names_by_id[alias_id]: _pretty_yaml_soa(soa) for alias_id, soa in alias_soa}


def _get_all_domains(plesk_api, backup):
    """Get all domains from Plesk 9
    Returns id-name pairs for each domain
    """
    return _execute_plesk_api_splitted(
        Plesk9ApiHelper(plesk_api).get_domain_id_name_pair,
        BackupHelper(backup).get_all_subscription_names(), 
    )


def _get_all_sites(backup, plesk_api):
    backup_helper = BackupHelper(backup)
    plesk_api_helper = Plesk10ApiHelper(plesk_api)

    return _execute_plesk_api_splitted(plesk_api_helper.get_subscription_id_name_pair, backup_helper.get_all_subscription_names()) + \
        _execute_plesk_api_splitted(plesk_api_helper.get_addon_domain_id_name_pair, backup_helper.get_all_addon_names()) + \
        _execute_plesk_api_splitted(plesk_api_helper.get_subdomain_id_name_pair, backup_helper.get_all_subdomain_names())


def _get_all_aliases(plesk_api, backup):
    """Returns id-name pairs for each domain alias
    """
    return _execute_plesk_api_splitted(
        _get_plesk_api_helper(plesk_api).get_alias_id_name_pair, 
        BackupHelper(backup).get_all_alias_names()
    )


def _pretty_yaml_soa(soa):
    return SOA(ttl=soa.ttl, refresh=soa.refresh, retry=soa.retry, expire=soa.expire, minimum=soa.minimum)


def _execute_plesk_api_splitted(method, filter_items):
    result = []
    for filter_items_batch in split_list_by_count(filter_items, MAX_PLESK_API_FILTER_LENGTH):
        result.extend(method(filter_items_batch))
    return result


def _execute_plesk_api_splitted_no_result(method, filter_items):
    for filter_items_batch in split_list_by_count(filter_items, MAX_PLESK_API_FILTER_LENGTH):
        method(filter_items_batch)


def _send_and_check(plesk_api, operator):
    responses = plesk_api.send(operator)
    for response in responses:
        response.check()


def _get_plesk_api_helper(plesk_api):
    if version_tuple(plesk_api.api_version) <= (1, 6, 0, 2):
        return Plesk9ApiHelper(plesk_api)
    else:
        return Plesk10ApiHelper(plesk_api)


class Plesk10ApiHelper:
    def __init__(self, plesk_api):
        self.plesk_api = plesk_api

    def get_subscription_id_name_pair(self, subscription_names):
        return [
            (subscription.data[0], subscription.data[1].gen_info.name)
            for subscription in self.plesk_api.send(SubscriptionOperator.Get(
                filter=SubscriptionOperator.FilterByName(subscription_names),
                dataset=[SubscriptionOperator.Dataset.GEN_INFO]
            ))
        ]

    def get_addon_domain_id_name_pair(self, addon_domain_names):
        return [
            (site.data[0], site.data[1].gen_info.name)
            for site in self.plesk_api.send(SiteOperator.Get(
                filter=SiteOperator.FilterByName(addon_domain_names),
                dataset=[SiteOperator.Dataset.GEN_INFO]
            ))
        ]

    def get_subdomain_id_name_pair(self, subdomain_names):
        return [
            (int(subdomain.data[0]), subdomain.data[1].name)
            for subdomain in self.plesk_api.send(SubdomainOperator.Get(
                filter=SubdomainOperator.FilterByName(subdomain_names),
            ))
        ]

    def get_alias_id_name_pair(self, alias_names):
        return [
            (alias.data[0], alias.data[1].name)
            for alias in self.plesk_api.send(AliasOperator.Get(
                filter=AliasOperator.FilterByName(alias_names),
            ))
        ]

    def get_soa_for_sites(self, site_ids):
        return [
            (result.data.object_id, result.data.soa)
            for result in self.plesk_api.send(DnsOperator.Get(
                filter=DnsOperator.FilterBySiteId(site_ids),
                soa=True
            ))
        ]

    def get_soa_for_aliases(self, alias_ids):
        return [
            (result.data.object_id, result.data.soa)
            for result in self.plesk_api.send(DnsOperator.Get(
                filter=DnsOperator.FilterBySiteAliasId(alias_ids),
                soa=True
            ))
        ]

    def set_soa_for_sites(self, site_ids, soa):
        _send_and_check(self.plesk_api, DnsOperator.Set(
            filter=DnsOperator.FilterBySiteId(site_ids),
            soa=soa 
        ))

    def set_soa_for_aliases(self, alias_ids, soa):
        _send_and_check(self.plesk_api, DnsOperator.Set(
            filter=DnsOperator.FilterBySiteAliasId(alias_ids),
            soa=soa
        ))


class Plesk9ApiHelper:
    def __init__(self, plesk_api):
        self.plesk_api = plesk_api

    def get_domain_id_name_pair(self, domain_names):
        if version_tuple(self.plesk_api.api_version) < (1, 6, 0, 0):
            filter = DomainOperator.FilterByName8(domain_names)
        else:
            filter = DomainOperator.FilterByDomainName(domain_names)
        return [
            (site.data[0], site.data[1].gen_info.name)
            for site in self.plesk_api.send(DomainOperator.Get(
                filter=filter,
                dataset=[DomainOperator.Dataset.GEN_INFO]
            ))
        ]
    
    def get_alias_id_name_pair(self, alias_names):
        return [
            (alias.data[0], alias.data[1].name)
            for alias in self.plesk_api.send(DomainAliasOperator.Get(
                filter=DomainAliasOperator.FilterByName(alias_names),
            ))
        ]

    def get_soa_for_domains(self, domain_ids):
        return [
            (result.data.object_id, result.data.soa)
            for result in self.plesk_api.send(DnsOperator9.Get(
                filter=DnsOperator9.FilterByDomainId(domain_ids),
                soa=True
            ))
        ]

    def get_soa_for_aliases(self, alias_ids):
        return [
            (result.data.object_id, result.data.soa)
            for result in self.plesk_api.send(DnsOperator9.Get(
                filter=DnsOperator9.FilterByDomainAliasId(alias_ids),
                soa=True
            ))
        ]

    def set_soa_for_domains(self, domain_ids, soa):
        _send_and_check(self.plesk_api, DnsOperator9.Set(
            filter=DnsOperator9.FilterByDomainId(domain_ids),
            soa=soa 
        ))

    def set_soa_for_aliases(self, alias_ids, soa):
        _send_and_check(self.plesk_api, DnsOperator9.Set(
            filter=DnsOperator9.FilterByDomainAliasId(alias_ids),
            soa=soa
        ))


class BackupHelper:
    def __init__(self, backup):
        self.backup = backup

    def get_all_subscription_names(self):
        return [subscription.name for subscription in self.backup.iter_all_subscriptions()]

    def get_all_addon_names(self):
        return [
            addon_domain.name
            for subscription_name in self.get_all_subscription_names()
            for addon_domain in self.backup.iter_addon_domains(subscription_name)
        ]

    def get_all_subdomain_names(self):
        return [
            subdomain.name
            for subscription_name in self.get_all_subscription_names()
            for domain_name in itertools.chain(
                [subscription_name], 
                [addon_domain.name for addon_domain in self.backup.iter_addon_domains(subscription_name)]
            )
            for subdomain in self.backup.iter_subdomains(subscription_name, domain_name)
        ]

    def get_all_alias_names(self):
        return [
            alias.name
            for subscription_name in self.get_all_subscription_names()
            for alias in self.backup.iter_aliases(subscription_name)
        ]
