# -*- coding: utf-8 -*-
'''
Module for gathering and managing network information
'''
from __future__ import absolute_import, unicode_literals, print_function

# Import Python libs
import re
import hashlib
import datetime
import socket

# Import Salt libs
import salt.utils.network
import salt.utils.platform
import salt.utils.validate.net
from salt.modules.network import (wol, get_hostname, interface, interface_ip,
                                  subnets6, ip_in_subnet, convert_cidr,
                                  calc_net, get_fqdn, ifacestartswith,
                                  iphexval)
from salt.utils.functools import namespaced_function as _namespaced_function

try:
    import salt.utils.winapi
    HAS_DEPENDENCIES = True
except ImportError:
    HAS_DEPENDENCIES = False

# Import 3rd party libraries
import salt.ext.six as six  # pylint: disable=W0611
try:
    import wmi  # pylint: disable=W0611
except ImportError:
    HAS_DEPENDENCIES = False

# Define the module's virtual name
__virtualname__ = 'network'


def __virtual__():
    '''
    Only works on Windows systems
    '''
    if not salt.utils.platform.is_windows():
        return False, "Module win_network: Only available on Windows"

    if not HAS_DEPENDENCIES:
        return False, "Module win_network: Missing dependencies"

    global wol, get_hostname, interface, interface_ip, subnets6, ip_in_subnet
    global convert_cidr, calc_net, get_fqdn, ifacestartswith, iphexval

    wol = _namespaced_function(wol, globals())
    get_hostname = _namespaced_function(get_hostname, globals())
    interface = _namespaced_function(interface, globals())
    interface_ip = _namespaced_function(interface_ip, globals())
    subnets6 = _namespaced_function(subnets6, globals())
    ip_in_subnet = _namespaced_function(ip_in_subnet, globals())
    convert_cidr = _namespaced_function(convert_cidr, globals())
    calc_net = _namespaced_function(calc_net, globals())
    get_fqdn = _namespaced_function(get_fqdn, globals())
    ifacestartswith = _namespaced_function(ifacestartswith, globals())
    iphexval = _namespaced_function(iphexval, globals())

    return __virtualname__


def ping(host, timeout=False, return_boolean=False):
    '''
    Performs a ping to a host

    CLI Example:

    .. code-block:: bash

        salt '*' network.ping archlinux.org

    .. versionadded:: 2016.11.0

    Return a True or False instead of ping output.

    .. code-block:: bash

        salt '*' network.ping archlinux.org return_boolean=True

    Set the time to wait for a response in seconds.

    .. code-block:: bash

        salt '*' network.ping archlinux.org timeout=3
    '''
    if timeout:
        # Windows ping differs by having timeout be for individual echo requests.'
        # Divide timeout by tries to mimic BSD behaviour.
        timeout = int(timeout) * 1000 // 4
        cmd = ['ping', '-n', '4', '-w', six.text_type(timeout), salt.utils.network.sanitize_host(host)]
    else:
        cmd = ['ping', '-n', '4', salt.utils.network.sanitize_host(host)]
    if return_boolean:
        ret = __salt__['cmd.run_all'](cmd, python_shell=False)
        if ret['retcode'] != 0:
            return False
        else:
            return True
    else:
        return __salt__['cmd.run'](cmd, python_shell=False)


def netstat():
    '''
    Return information on open ports and states

    CLI Example:

    .. code-block:: bash

        salt '*' network.netstat
    '''
    ret = []
    cmd = ['netstat', '-nao']
    lines = __salt__['cmd.run'](cmd, python_shell=False).splitlines()
    for line in lines:
        comps = line.split()
        if line.startswith('  TCP'):
            ret.append({
                'local-address': comps[1],
                'proto': comps[0],
                'remote-address': comps[2],
                'state': comps[3],
                'program': comps[4]})
        if line.startswith('  UDP'):
            ret.append({
                'local-address': comps[1],
                'proto': comps[0],
                'remote-address': comps[2],
                'state': None,
                'program': comps[3]})
    return ret


def traceroute(host):
    '''
    Performs a traceroute to a 3rd party host

    CLI Example:

    .. code-block:: bash

        salt '*' network.traceroute archlinux.org
    '''
    ret = []
    cmd = ['tracert', salt.utils.network.sanitize_host(host)]
    lines = __salt__['cmd.run'](cmd, python_shell=False).splitlines()
    for line in lines:
        if ' ' not in line:
            continue
        if line.startswith('Trac'):
            continue
        if line.startswith('over'):
            continue
        comps = line.split()
        complength = len(comps)
        # This method still needs to better catch rows of other lengths
        # For example if some of the ms returns are '*'
        if complength == 9:
            result = {
                'count': comps[0],
                'hostname': comps[7],
                'ip': comps[8],
                'ms1': comps[1],
                'ms2': comps[3],
                'ms3': comps[5]}
            ret.append(result)
        elif complength == 8:
            result = {
                'count': comps[0],
                'hostname': None,
                'ip': comps[7],
                'ms1': comps[1],
                'ms2': comps[3],
                'ms3': comps[5]}
            ret.append(result)
        else:
            result = {
                'count': comps[0],
                'hostname': None,
                'ip': None,
                'ms1': None,
                'ms2': None,
                'ms3': None}
            ret.append(result)
    return ret


def nslookup(host):
    '''
    Query DNS for information about a domain or ip address

    CLI Example:

    .. code-block:: bash

        salt '*' network.nslookup archlinux.org
    '''
    ret = []
    addresses = []
    cmd = ['nslookup', salt.utils.network.sanitize_host(host)]
    lines = __salt__['cmd.run'](cmd, python_shell=False).splitlines()
    for line in lines:
        if addresses:
            # We're in the last block listing addresses
            addresses.append(line.strip())
            continue
        if line.startswith('Non-authoritative'):
            continue
        if 'Addresses' in line:
            comps = line.split(":", 1)
            addresses.append(comps[1].strip())
            continue
        if ":" in line:
            comps = line.split(":", 1)
            ret.append({comps[0].strip(): comps[1].strip()})
    if addresses:
        ret.append({'Addresses': addresses})
    return ret


def get_route(ip):
    '''
    Return routing information for given destination ip

    .. versionadded:: 2016.11.5

    CLI Example::

        salt '*' network.get_route 10.10.10.10
    '''
    cmd = 'Find-NetRoute -RemoteIPAddress {0}'.format(ip)
    out = __salt__['cmd.run'](cmd, shell='powershell', python_shell=True)
    regexp = re.compile(
        r"^IPAddress\s+:\s(?P<source>[\d\.:]+)?.*"
        r"^InterfaceAlias\s+:\s(?P<interface>[\w\.\:\-\ ]+)?.*"
        r"^NextHop\s+:\s(?P<gateway>[\d\.:]+)",
        flags=re.MULTILINE | re.DOTALL
    )
    m = regexp.search(out)
    ret = {
        'destination': ip,
        'gateway': m.group('gateway'),
        'interface': m.group('interface'),
        'source': m.group('source')
    }

    return ret


def dig(host):
    '''
    Performs a DNS lookup with dig

    Note: dig must be installed on the Windows minion

    CLI Example:

    .. code-block:: bash

        salt '*' network.dig archlinux.org
    '''
    cmd = ['dig', salt.utils.network.sanitize_host(host)]
    return __salt__['cmd.run'](cmd, python_shell=False)


def interfaces_names():
    '''
    Return a list of all the interfaces names

    CLI Example:

    .. code-block:: bash

        salt '*' network.interfaces_names
    '''

    ret = []
    with salt.utils.winapi.Com():
        c = wmi.WMI()
        for iface in c.Win32_NetworkAdapter(NetEnabled=True):
            ret.append(iface.NetConnectionID)
    return ret


def interfaces():
    '''
    Return a dictionary of information about all the interfaces on the minion

    CLI Example:

    .. code-block:: bash

        salt '*' network.interfaces
    '''
    return salt.utils.network.win_interfaces()


def hw_addr(iface):
    '''
    Return the hardware address (a.k.a. MAC address) for a given interface

    CLI Example:

    .. code-block:: bash

        salt '*' network.hw_addr 'Wireless Connection #1'
    '''
    return salt.utils.network.hw_addr(iface)

# Alias hwaddr to preserve backward compat
hwaddr = salt.utils.functools.alias_function(hw_addr, 'hwaddr')


def subnets():
    '''
    Returns a list of subnets to which the host belongs

    CLI Example:

    .. code-block:: bash

        salt '*' network.subnets
    '''
    return salt.utils.network.subnets()


def in_subnet(cidr):
    '''
    Returns True if host is within specified subnet, otherwise False

    CLI Example:

    .. code-block:: bash

        salt '*' network.in_subnet 10.0.0.0/16
    '''
    return salt.utils.network.in_subnet(cidr)


def ip_addrs(interface=None, include_loopback=False):
    '''
    Returns a list of IPv4 addresses assigned to the host. 127.0.0.1 is
    ignored, unless 'include_loopback=True' is indicated. If 'interface' is
    provided, then only IP addresses from that interface will be returned.

    CLI Example:

    .. code-block:: bash

        salt '*' network.ip_addrs
    '''
    return salt.utils.network.ip_addrs(interface=interface,
                                       include_loopback=include_loopback)

ipaddrs = salt.utils.functools.alias_function(ip_addrs, 'ipaddrs')


def ip_addrs6(interface=None, include_loopback=False):
    '''
    Returns a list of IPv6 addresses assigned to the host. ::1 is ignored,
    unless 'include_loopback=True' is indicated. If 'interface' is provided,
    then only IP addresses from that interface will be returned.

    CLI Example:

    .. code-block:: bash

        salt '*' network.ip_addrs6
    '''
    return salt.utils.network.ip_addrs6(interface=interface,
                                        include_loopback=include_loopback)

ipaddrs6 = salt.utils.functools.alias_function(ip_addrs6, 'ipaddrs6')


def connect(host, port=None, **kwargs):
    '''
    Test connectivity to a host using a particular
    port from the minion.

    .. versionadded:: 2016.3.0

    CLI Example:

    .. code-block:: bash

        salt '*' network.connect archlinux.org 80

        salt '*' network.connect archlinux.org 80 timeout=3

        salt '*' network.connect archlinux.org 80 timeout=3 family=ipv4

        salt '*' network.connect google-public-dns-a.google.com port=53 proto=udp timeout=3
    '''

    ret = {'result': None,
           'comment': ''}

    if not host:
        ret['result'] = False
        ret['comment'] = 'Required argument, host, is missing.'
        return ret

    if not port:
        ret['result'] = False
        ret['comment'] = 'Required argument, port, is missing.'
        return ret

    proto = kwargs.get('proto', 'tcp')
    timeout = kwargs.get('timeout', 5)
    family = kwargs.get('family', None)

    if salt.utils.validate.net.ipv4_addr(host) or salt.utils.validate.net.ipv6_addr(host):
        address = host
    else:
        address = '{0}'.format(salt.utils.network.sanitize_host(host))

    try:
        if proto == 'udp':
            __proto = socket.SOL_UDP
        else:
            __proto = socket.SOL_TCP
            proto = 'tcp'

        if family:
            if family == 'ipv4':
                __family = socket.AF_INET
            elif family == 'ipv6':
                __family = socket.AF_INET6
            else:
                __family = 0
        else:
            __family = 0

        (family,
         socktype,
         _proto,
         garbage,
         _address) = socket.getaddrinfo(address, port, __family, 0, __proto)[0]

        skt = socket.socket(family, socktype, _proto)
        skt.settimeout(timeout)

        if proto == 'udp':
            # Generate a random string of a
            # decent size to test UDP connection
            md5h = hashlib.md5()
            md5h.update(datetime.datetime.now().strftime('%s'))
            msg = md5h.hexdigest()
            skt.sendto(msg, _address)
            recv, svr = skt.recvfrom(255)
            skt.close()
        else:
            skt.connect(_address)
            skt.shutdown(2)
    except Exception as exc:
        ret['result'] = False
        ret['comment'] = 'Unable to connect to {0} ({1}) on {2} port {3}'\
            .format(host, _address[0], proto, port)
        return ret

    ret['result'] = True
    ret['comment'] = 'Successfully connected to {0} ({1}) on {2} port {3}'\
        .format(host, _address[0], proto, port)
    return ret
