from ipaddress import IPv4Address, IPv6Address, ip_address

from bundlewrap.metadata import atomic

defaults = {
    'apt': {
        'packages': {
            'pdns-server': {},
            'pdns-tools': {},
            'pdns-backend-bind': {},
            'pdns-backend-pgsql': {},
        },
    },
    'icinga2_api': {
        'powerdns': {
            'services': {
                'POWERDNS PROCESS': {
                    'command_on_monitored_host': '/usr/lib/nagios/plugins/check_procs -C pdns_server -c 1:',
                    'vars.notification.mail': True,
                    'vars.notification.sms': True,
                },
            },
        },
    },
    'powerdns': {
        'api_key': repo.vault.password_for('{} powerdns api'.format(node.name)),
    },
    'postgresql': {
        'roles': {
            'powerdns': {
                'password': repo.vault.password_for('{} postgresql powerdns'.format(node.name)),
            },
        },
        'databases': {
            'powerdns': {
                'owner': 'powerdns',
            },
        },
    },
}

if node.has_bundle('telegraf'):
    defaults['telegraf'] = {
        'input_plugins': {
            'builtin': {
                'powerdns': [{
                    'unix_sockets': [
                        '/var/run/pdns/pdns.controlsocket',
                    ],
                }],
            },
        },
        'additional_groups': {
            'pdns',
        },
    }


@metadata_reactor.provides(
    'icinga2_api/powerdns/services',
)
def monitoring_for_primary_nameserver(metadata):
    if metadata.get('powerdns/is_secondary', False):
        return {}

    return {
        'icinga2_api': {
            'powerdns': {
                'services': {
                    'POWERDNS WEB INTERFACE': {
                        'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_http_url_for_string http://localhost:8081/ "PowerDNS"',
                    },
                },
            },
        },
    }


@metadata_reactor.provides(
    'powerdns/my_secondary_servers',
)
def get_ips_of_secondary_nameservers(metadata):
    if metadata.get('powerdns/is_secondary', False):
        return {}

    ips = set()
    for rnode in repo.nodes_in_group('dns'):
        if rnode.metadata.get('powerdns/is_secondary', False):
            if rnode.name == node.name:
                raise BundleError(f'{node.name} cannot be its own secondary')
            for _, found_ips in repo.libs.tools.resolve_identifier(repo, rnode.name).items():
                ips.update({str(ip) for ip in found_ips})

    return {
        'powerdns': {
            'my_secondary_servers': ips,
        },
    }

@metadata_reactor.provides(
    'powerdns/my_primary_servers',
)
def get_ips_of_primary_nameservers(metadata):
    if not metadata.get('powerdns/is_secondary', False):
        return {}

    ips = {}
    for rnode in repo.nodes_in_group('dns'):
        if not rnode.metadata.get('powerdns/is_secondary', False):
            if rnode.name == node.name:
                raise BundleError(f'{node.name} cannot be its own secondary')
            hostname = rnode.metadata.get('hostname')
            ips[hostname] = set()
            for _, found_ips in repo.libs.tools.resolve_identifier(repo, rnode.name).items():
                ips[hostname].update({str(ip) for ip in found_ips})

    return {
        'powerdns': {
            'my_primary_servers': ips,
        },
    }


@metadata_reactor.provides(
    'powerdns/bind-zones/kunbox.net/records',
)
def generate_dns_entries_for_nodes(metadata):
    results = set()

    for rnode in repo.nodes:
        node_name_split = rnode.name.split('.')
        node_name_split.reverse()
        dns_name = '.'.join(node_name_split)
        ip4 = None
        ip6 = None

        found_ips = repo.libs.tools.resolve_identifier(repo, rnode.name, only_physical=True)
        for ip in sorted(found_ips['ipv4']):
            if not ip4 and not ip.is_private:
                ip4 = ip

        for ip in sorted(found_ips['ipv6']):
            if not ip6 and not ip.is_private:
                ip6 = ip

        if not (ip4 or ip6) and (found_ips['ipv4'] or found_ips['ipv6']):
            # do it again, but do not filter out private addresses
            for ip in sorted(found_ips['ipv4']):
                if not ip4:
                    ip4 = ip
            for ip in sorted(found_ips['ipv6']):
                if not ip6:
                    ip6 = ip

        if ip4:
            results.add('{} IN A {}'.format(dns_name, ip4))

        if ip6:
            results.add('{} IN AAAA {}'.format(dns_name, ip6))

    return {
        'powerdns': {
            'bind-zones': {
                'kunbox.net': {
                    'records': results,
                },
            },
        },
    }


@metadata_reactor.provides(
    'hosts/entries',
)
def hosts_entries_for_all_dns_servers(metadata):
    entries = {}

    for rnode in repo.nodes_in_group('dns'):
        if rnode.name == node.name:
            continue

        found_ips = repo.libs.tools.resolve_identifier(repo, rnode.name)
        for ip in sorted(found_ips['ipv4']):
            if not ip.is_private:
                entries[str(ip)] = {
                    rnode.metadata.get('hostname'),
                    rnode.name,
                }

                if rnode.metadata.get('powerdns/my_hostname', None):
                    entries[str(ip)].add(rnode.metadata.get('powerdns/my_hostname'))

    return {
        'hosts': {
            'entries': entries,
        },
    }


@metadata_reactor.provides(
    'firewall/port_rules',
)
def firewall(metadata):
    return {
        'firewall': {
            'port_rules': {
                '53/tcp': atomic(metadata.get('powerdns/restrict-to/dns', {'*'})),
                '53/udp': atomic(metadata.get('powerdns/restrict-to/dns', {'*'})),
                '8081/tcp': atomic(metadata.get('powerdns/restrict-to/api', set())),
            },
        },
    }
