import os
import re
import sys
import yaml
import socket


class DataSource(object):

    def __init__(self):
        self._groups = {}

    def lookup(self, hostname):
        raise NotImplemented('subclass must implement lookup')

    def all_hosts(self):
        raise NotImplemented('subclass must implement all_hosts')

    def groups(self):
        """
        Return {group: {members: [hosts]}} data that is used in creating hostgroups
        """
        return self._groups

    def add_members_to_group(self, group, hosts):
        """
        Add hosts (FQDNs) to a group.
        :type group: str
        :type hosts: [str]
        """
        if not hosts:
            return
        if group not in self._groups:
            self._groups[group] = {'members': []}
        for this in hosts:
            if this not in self._groups[group]['members']:
                self._groups[group]['members'] += [this]

    def load_classes(self, filename):
        with open(filename) as fd:
            _classes = yaml.safe_load(fd)
            for this_r, classes in _classes.items():
                r_comp = re.compile(this_r)
                hosts = []
                for host in self.all_hosts():
                    if re.match(r_comp, host):
                        hosts += [host]
                if not classes:
                    continue
                for cls in classes.keys():
                    parts = cls.split('::')
                    if parts[0] in ['sunet', 'eduid', 'thulin']:
                        parts = parts[1:]
                    cls = '_'.join(parts)
                    self.add_members_to_group(cls, hosts)


class DNSResolver(DataSource):
    """
    DNS based hostname-to-IPs resolver, returning both IPv4 and IPv6 addresses.
    """

    def __init__(self, suffixes):
        super(DNSResolver, self).__init__()
        self._suffixes = suffixes

    def lookup(self, hostname):
        res = gai4 = gai6 = []
        try:
            gai4 = socket.getaddrinfo(hostname, 0, socket.AF_INET, 0, socket.IPPROTO_TCP)
        except socket.gaierror:
            sys.stderr.write('Host {} not found in DNS (v4)\n'.format(hostname))
        try:
            gai6 = socket.getaddrinfo(hostname, 0, socket.AF_INET6, 0, socket.IPPROTO_TCP)
        except socket.gaierror:
            sys.stderr.write('Host {} not found in DNS (v6)\n'.format(hostname))
        for this in gai4 + gai6:
            res.append(this[4][0])
        return res

    def all_hosts(self):
        """
        Return a list of all hosts in this cosmos environment.

        Currently this is done by looking for subdirectorys with a '.eduid.se' in them,
        but not starting with a '.'.
        """
        hosts = filter(lambda fn: os.path.isdir(fn) and \
                       not fn.startswith('.'),

                       os.listdir('.'))
        matching = []
        for h in hosts:
            for s in self._suffixes:
                if h.endswith(s):
                    matching += [h]
                    break
        return sorted(matching)


class HostsResolver(DataSource):
    """
    Return data from a YAML file with entrys like this:

    site_hosts:
       host1_vlan10:
          addrs:
          - 192.168.10.1
          dhcp: false
          fqdn: host1.example.org
          mac: xxx
       host2_vlan2:
          addrs:
            - 192.168.2.2
            - 2001::1
            - 2001::2
          dhcp: true
          fqdn: host2.example.org
          mac: yyy
          type: server
    """

    def __init__(self, fn='global/overlay/etc/puppet/hiera/data/hosts.yaml'):
        super(HostsResolver, self).__init__()
        self._fn = fn
        self._data = _load_hosts_file(fn)

    def lookup(self, hostname):
        hostname = hostname.lower()
        res = []
        for this, values in self._data.items():
            fqdn = self._get_fqdn(this)
            if not fqdn:
                continue
            if fqdn == hostname:
                res += values.get('addrs', [])
        return res

    def all_hosts(self):
        """
        Return a list of all FQDNs in this cosmos environment.
        """
        res = {}
        for this in self._data.keys():
            fqdn = self._get_fqdn(this)
            if not fqdn:
                continue
            res[fqdn] = True
        return res.keys()

    def groups(self):
        """
        Create hostgroups based on 'type' in the hosts data.
        """
        res = super(HostsResolver, self).groups()
        for this, values in self._data.items():
            if 'type' in values:
                group = values['type'].lower()
                fqdn = self._get_fqdn(this)
                if fqdn is None:
                    continue
                self.add_members_to_group(group, [fqdn])
        return res

    def _get_fqdn(self, key):
        fqdn = self._data[key]['fqdn'].lower()
        if '-old.' in fqdn:
            return None
        if self._data[key].get('monitor') is False:
            return None
        return fqdn


def _load_hosts_file(fn):
    """
    Load site-hosts data from hosts.yaml.
    """
    with open(fn) as fd:
        data = yaml.load(fd)
    return data['site_hosts']