# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import netifaces
import socket
import logging
from ..exceptions import NetworkError
from .shell import ScriptRunner

netaddr_available = True
try:
    import netaddr
except ImportError:
    netaddr_available = False


def get_localhost_ip():
    """
    Returns IP address of localhost.
    """
    # Try to get the IPv4 or IPv6 default gateway, then open a socket
    # to discover our local IP
    gw = None
    for protocol in (socket.AF_INET, socket.AF_INET6):
        try:
            gw = netifaces.gateways()['default'][protocol][0]
            if protocol == socket.AF_INET6:
                gw = gw + '%' + netifaces.gateways()['default'][protocol][1]
            discovered_protocol = protocol
            break
        except KeyError:    # No default gw for this protocol
            continue
    else:
        raise NetworkError('Local IP address discovery failed. Please set '
                           'a default gateway for your system.')

    address = socket.getaddrinfo(gw, 0, discovered_protocol,
                                 socket.SOCK_DGRAM)[0]
    s = socket.socket(discovered_protocol, socket.SOCK_DGRAM)
    s.connect(address[4])
    # Remove chars after %. Does nothing on IPv4, removes scope id in IPv6
    loc_ip = s.getsockname()[0].split('%')[0]

    return loc_ip


_host_cache = {}


def host2ip(hostname, allow_localhost=False):
    """
    Converts given hostname to IP address. Raises NetworkError
    if conversion failed.
    """
    key = '{}:{}'.format(hostname, allow_localhost)
    if key in _host_cache:
        return _host_cache[key]
    try:
        ip_list = list(sockets[4][0] for sockets in
                       socket.getaddrinfo(hostname, 22, 0, 0, socket.IPPROTO_TCP))

        if allow_localhost:
            ip = ip_list[0]
        else:
            routable = [ip for ip in ip_list if ip not in ('127.0.0.1', '::1')]
            if not routable:
                raise NameError("Host %s is not routable, please fix"
                                "your /etc/hosts", host)
            if len(routable) > 1:
                logging.warning("Multiple IPs for host detected!")
            ip = routable[0]

        _host_cache[key] = ip
        return ip
    except NameError:
        # given hostname is localhost, return appropriate IP address
        return get_localhost_ip()
    except socket.error:
        raise NetworkError('Unknown hostname %s.' % hostname)
    except Exception as ex:
        raise NetworkError('Unknown error appeared: %s' % repr(ex))


def is_ipv6(host):
    if not netaddr_available:
        raise ImportError(
            "netaddr module unavailable, install with pip install netaddr"
        )
    host = host.strip()
    try:
        return netaddr.IPAddress(host.strip('[]')).version == 6
    except netaddr.core.AddrFormatError:
        # Most probably a hostname
        return False


def is_ipv4(host):
    if not netaddr_available:
        raise ImportError(
            "netaddr module unavailable, install with pip install netaddr"
        )
    host = host.strip()
    try:
        return netaddr.IPAddress(host).version == 4
    except netaddr.core.AddrFormatError:
        # Most probably a hostname
        return False


def force_ip(host, allow_localhost=False):
    if not(is_ipv6(host) or is_ipv4(host)):
        host = host2ip(host, allow_localhost=allow_localhost)
    return host


def device_from_ip(ip):
    server = ScriptRunner()
    server.append("DEVICE=($(ip -o address show to %s | cut -f 2 -d ' '))"
                  % ip)
    # Ensure that the IP is only assigned to one interface
    server.append("if [ ! -z ${DISPLAY[1]} ]; then false; fi")
    # Test device, raises an exception if it doesn't exist
    server.append("ip link show \"$DEVICE\" > /dev/null")
    server.append("echo $DEVICE")
    rv, stdout = server.execute()
    return stdout.strip()
