from parallels.core.logging import get_logger
from parallels.core.utils.common import safe_string_repr
from parallels.hosting_check.utils.dns_utils import \
    DnsRequestTimeoutException, get_difference_between_dns_servers, \
    get_authoritative_dns_servers
from parallels.hosting_check.utils.text_utils import format_list
from parallels.hosting_check import DomainIssue, Severity
from parallels.hosting_check import DNSExternalIssueType
from parallels.hosting_check.messages import MSG

logger = get_logger(__name__)


class DNSExternalChecker(object):
    def __init__(
        self, external_dns_servers, max_difference_lines=3, 
        save_external_report_data_function=None
    ):
        self.external_dns_servers = external_dns_servers
        self.max_difference_lines = max_difference_lines
        self.save_external_report_data_function = \
            save_external_report_data_function

    def check(self, domains_to_check, ):
        issues = []

        for domain_to_check in domains_to_check:
            for external_dns_server_ip in self.external_dns_servers:
                self._check_single_external_server(
                    domain_to_check, external_dns_server_ip, issues
                )

        return issues

    def _check_single_external_server(
        self, domain_to_check, external_dns_server_ip, issues
    ):
        def add_issue(severity, category, problem):
            issues.append(
                DomainIssue(
                    domain_name=domain_to_check.domain_name, 
                    severity=severity, 
                    category=category, problem=problem
                )
            )

        logger.info(
            MSG(
                'dns_external_log_checking',
                server_ip=external_dns_server_ip
            )
        )
        try:
            dns_servers = get_authoritative_dns_servers(
                domain_to_check.domain_name, external_dns_server_ip
            )
            if len(dns_servers) == 0:
                add_issue(
                    Severity.INFO, DNSExternalIssueType.NOT_REGISTERED,
                    MSG(DNSExternalIssueType.NOT_REGISTERED)
                )
            else:
                unknown_dns_servers = [
                    dns_server 
                    for dns_server in dns_servers
                    if len(dns_server.ips) == 0
                ]
                if len(unknown_dns_servers) > 0:
                    add_issue(
                        Severity.INFO,
                        DNSExternalIssueType.SERVED_BY_UNKNOWN_DNS_SERVERS,
                        MSG(
                            DNSExternalIssueType.SERVED_BY_UNKNOWN_DNS_SERVERS,
                            dns_servers_list=format_list([
                                dns_server.hostname 
                                for dns_server in unknown_dns_servers
                            ])
                        )
                    )
                else:
                    dns_server_ips = set(
                        sum([dns_server.ips for dns_server in dns_servers], [])
                    )
                    target_dns_server_ips = set(
                        domain_to_check.target_dns_servers
                    )
                    source_panel_dns_server_ips = set(
                        domain_to_check.source_dns_servers
                    )
                    if dns_server_ips <= target_dns_server_ips | source_panel_dns_server_ips:
                        difference = get_difference_between_dns_servers(
                            domain_to_check.dns_records, 
                            domain_to_check.target_dns_servers[0], 
                            external_dns_server_ip
                        )

                        if len(difference) > 0:
                            self._report_difference(
                                domain_to_check, external_dns_server_ip, 
                                difference, issues
                            )
                    else:
                        add_issue(
                            Severity.INFO,
                            DNSExternalIssueType.NOT_SERVED_BY_SOURCE_OR_TARGET,
                            MSG(
                                DNSExternalIssueType.NOT_SERVED_BY_SOURCE_OR_TARGET
                            )
                        )
        except DnsRequestTimeoutException as e:
            logger.debug(u"Exception:", exc_info=e)
            # do not show any report comparing records - just a timeout message
            add_issue(
                Severity.WARNING,
                DNSExternalIssueType.DNS_SERVER_TIMEOUT,
                MSG(
                    DNSExternalIssueType.DNS_SERVER_TIMEOUT,
                    external_dns_server_ip=e.server_ip
                )
            )
        except Exception as e:
            logger.debug(u"Exception:", exc_info=e)
            # do not show any report comparing records - just a general failure
            # message
            add_issue(
                Severity.WARNING,
                DNSExternalIssueType.INTERNAL_ERROR,
                MSG(
                    DNSExternalIssueType.INTERNAL_ERROR,
                    reason=safe_string_repr(e),
                    external_dns_server_ip=external_dns_server_ip
                )
            )

    def _report_difference(
        self, domain_to_check, external_dns_server_ip, difference, issues
    ):
        if (
            len(difference) <= self.max_difference_lines or
            self.save_external_report_data_function is None
        ): 
            # put all differences to screen immediately
            difference_str = MSG(
                'dns_difference_full',
                difference=u"\n".join(difference)
            )
        else:
            # put first 3 differences to screen as an example, put all other
            # differences into a separate file
            filename = self.save_external_report_data_function(
                "dns.external.difference.%s" % domain_to_check.domain_name,
                MSG(
                    'dns_external_difference_file',
                    external_dns_server_ip=external_dns_server_ip,
                    target_dns_server_ip=domain_to_check.target_dns_servers[0],
                    difference=u"\n".join(difference)
                )
            )
            difference_str = MSG(
                'dns_external_difference_example',
                difference=u"\n".join(difference[:3]),
                filename=filename
            )

        issues.append(
            DomainIssue(
                domain_name=domain_to_check.domain_name,
                severity=Severity.WARNING,
                category=DNSExternalIssueType.DIFFERENT_RECORDS,
                problem=MSG(
                    DNSExternalIssueType.DIFFERENT_RECORDS,
                    external_dns_server_ip=external_dns_server_ip, 
                    target_dns_server_ip=domain_to_check.target_dns_servers[0],
                    difference=difference_str
                )
            )
        )
