# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

# utils


from __future__ import absolute_import, division, print_function


__metaclass__ = type
from functools import total_ordering

from ansible.module_utils._text import to_text
from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils.common.network import is_masklen, to_netmask
from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
    dict_diff,
    search_obj_in_list,
)


try:
    import ipaddress

    HAS_IPADDRESS = True
except ImportError:
    HAS_IPADDRESS = False


def remove_command_from_config_list(interface, cmd, commands):
    # To delete the passed config
    if interface not in commands:
        commands.insert(0, interface)
    commands.append("no %s" % cmd)
    return commands


def add_command_to_config_list(interface, cmd, commands):
    # To set the passed config
    if interface not in commands:
        commands.insert(0, interface)
    commands.append(cmd)


def dict_to_set(sample_dict):
    # Generate a set with passed dictionary for comparison
    test_dict = {}
    if isinstance(sample_dict, dict):
        for k, v in iteritems(sample_dict):
            if v is not None:
                if isinstance(v, list):
                    if isinstance(v[0], dict):
                        li = []
                        for each in v:
                            for key, value in iteritems(each):
                                if isinstance(value, list):
                                    each[key] = tuple(value)
                            li.append(tuple(iteritems(each)))
                        v = tuple(li)
                    else:
                        v = tuple(v)
                elif isinstance(v, dict):
                    li = []
                    for key, value in iteritems(v):
                        if isinstance(value, list):
                            v[key] = tuple(value)
                    li.extend(tuple(iteritems(v)))
                    v = tuple(li)
                test_dict.update({k: v})
        return_set = set(tuple(iteritems(test_dict)))
    else:
        return_set = set(sample_dict)
    return return_set


def filter_dict_having_none_value(want, have):
    # Generate dict with have dict value which is None in want dict
    test_dict = dict()
    test_key_dict = dict()
    name = want.get("name")
    if name:
        test_dict["name"] = name
    diff_ip = False
    want_ip = ""
    for k, v in iteritems(want):
        if isinstance(v, dict):
            for key, value in iteritems(v):
                if value is None and k in have and key in have.get(k):
                    dict_val = have.get(k).get(key)
                    test_key_dict.update({key: dict_val})
                test_dict.update({k: test_key_dict})
        if isinstance(v, list) and isinstance(v[0], dict):
            for key, value in iteritems(v[0]):
                if value is None and k in have and key in have.get(k):
                    dict_val = have.get(k).get(key)
                    test_key_dict.update({key: dict_val})
                test_dict.update({k: test_key_dict})
            # below conditions checks are added to check if
            # secondary IP is configured, if yes then delete
            # the already configured IP if want and have IP
            # is different else if it's same no need to delete
            for each in v:
                if each.get("secondary"):
                    want_ip = each.get("address").split("/")
                    have_ip = have.get("ipv4")
                    for each in have_ip:
                        if len(want_ip) > 1 and each.get("secondary"):
                            have_ip = each.get("address").split("/")[0]
                            if have_ip != want_ip[0]:
                                diff_ip = True
                    if each.get("secondary") and diff_ip is True:
                        test_key_dict.update({"secondary": True})
                    test_dict.update({"ipv4": test_key_dict})
                # Checks if want doesn't have secondary IP but have has secondary IP set
                elif have.get("ipv4"):
                    if [True for each_have in have.get("ipv4") if "secondary" in each_have]:
                        test_dict.update({"ipv4": {"secondary": True}})
        if k == "l2protocol":
            diff = True
            h_proto = have.get("l2protocol")
            w_proto = want.get("l2protocol")
            if h_proto:
                if w_proto:
                    for h in h_proto:
                        for w in w_proto:
                            if h == w:
                                diff = False
                        if not diff:
                            break
                else:
                    diff = True
            if diff:
                test_dict.update({k: v})
        if v is None:
            val = have.get(k)
            test_dict.update({k: val})
    return test_dict


def remove_duplicate_interface(commands):
    # Remove duplicate interface from commands
    set_cmd = []
    for each in commands:
        if "interface" in each:
            if each not in set_cmd:
                set_cmd.append(each)
        else:
            set_cmd.append(each)

    return set_cmd


def flatten_dict(x):
    result = {}
    if not isinstance(x, dict):
        return result

    for key, value in iteritems(x):
        if isinstance(value, dict):
            result.update(flatten_dict(value))
        else:
            result[key] = value

    return result


def dict_delete(base, comparable):
    """

    This function generates a dict containing key, value pairs for keys
    that are present in the `base` dict but not present in the `comparable`
    dict.

    :param base: dict object to base the diff on
    :param comparable: dict object to compare against base

    :returns: new dict object with key, value pairs that needs to be deleted.

    """
    to_delete = dict()

    for key in base:
        if isinstance(base[key], dict):
            sub_diff = dict_delete(base[key], comparable.get(key, {}))
            if sub_diff:
                to_delete[key] = sub_diff
        else:
            if key not in comparable:
                to_delete[key] = base[key]

    return to_delete


def pad_commands(commands, interface):
    commands.insert(0, "interface {0}".format(interface))


def diff_list_of_dicts(w, h, key="member"):
    """
    Returns a list containing diff between
    two list of dictionaries
    """
    if not w:
        w = []
    if not h:
        h = []

    diff = []
    for w_item in w:
        h_item = search_obj_in_list(w_item[key], h, key=key) or {}
        d = dict_diff(h_item, w_item)
        if d:
            if key not in d.keys():
                d[key] = w_item[key]
            diff.append(d)

    return diff


def validate_ipv4(value, module):
    if value:
        address = value.split("/")
        if len(address) != 2:
            module.fail_json(
                msg="address format is <ipv4 address>/<mask>, got invalid format {0}".format(
                    value,
                ),
            )

        if not is_masklen(address[1]):
            module.fail_json(
                msg="invalid value for mask: {0}, mask should be in range 0-32".format(
                    address[1],
                ),
            )


def validate_ipv6(value, module):
    if value:
        address = value.split("/")
        if len(address) != 2:
            module.fail_json(
                msg="address format is <ipv6 address>/<mask>, got invalid format {0}".format(
                    value,
                ),
            )
        else:
            if not 0 <= int(address[1]) <= 128:
                module.fail_json(
                    msg="invalid value for mask: {0}, mask should be in range 0-128".format(
                        address[1],
                    ),
                )


def validate_n_expand_ipv4(module, want):
    # Check if input IPV4 is valid IP and expand IPV4 with its subnet mask
    ip_addr_want = want.get("address")
    if len(ip_addr_want.split(" ")) > 1:
        return ip_addr_want
    validate_ipv4(ip_addr_want, module)
    ip = ip_addr_want.split("/")
    if len(ip) == 2:
        ip_addr_want = "{0} {1}".format(ip[0], to_netmask(ip[1]))

    return ip_addr_want


def normalize_interface(name):
    """Return the normalized interface name"""
    if not name:
        return

    def _get_number(name):
        digits = ""
        for char in name:
            if char.isdigit() or char in "/.":
                digits += char
        return digits

    if name.lower().startswith("gi"):
        if_type = "GigabitEthernet"
    elif name.lower().startswith("fa"):
        if_type = "FastEthernet"
    elif name.lower().startswith("for"):
        if_type = "FortyGigE"
    elif name.lower().startswith("te"):
        if_type = "TenGigE"
    elif name.lower().startswith("twe"):
        if_type = "TwentyFiveGigE"
    elif name.lower().startswith("hu"):
        if_type = "HundredGigE"
    elif name.lower().startswith("vl"):
        if_type = "Vlan"
    elif name.lower().startswith("lo"):
        if_type = "Loopback"
    elif name.lower().startswith("bundle-e"):
        if_type = "Bundle-Ether"
    elif name.lower().startswith("bundle-p"):
        if_type = "Bundle-POS"
    elif name.lower().startswith("fif"):
        if_type = "FiftyGigE"
    elif name.lower().startswith("fou"):
        if_type = "FourHundredGigE"
    elif name.lower().startswith("two"):
        if_type = "TwoHundredGigE"
    else:
        if_type = None

    number_list = name.split(" ")
    if len(number_list) == 2:
        number = number_list[-1].strip()
    else:
        number = _get_number(name)
    if if_type:
        proper_interface = if_type + number
    else:
        proper_interface = name

    return proper_interface


def get_interface_type(interface):
    """Gets the type of interface"""

    if interface.upper().startswith("GI"):
        return "GigabitEthernet"
    elif interface.upper().startswith("FA"):
        return "FastEthernet"
    elif interface.upper().startswith("FORT"):
        return "FortyGigE"
    elif interface.upper().startswith("ET"):
        return "Ethernet"
    elif interface.upper().startswith("LO"):
        return "Loopback"
    elif interface.upper().startswith("BUNDLE-E"):
        return "Bundle-Ether"
    elif interface.upper().startswith("BUNDLE-P"):
        return "Bundle-POS"
    elif interface.upper().startswith("NV"):
        return "nve"
    elif interface.upper().startswith("TE"):
        return "TenGigE"
    elif interface.upper().startswith("TWE"):
        return "TwentyFiveGigE"
    elif interface.upper().startswith("HU"):
        return "HundredGigE"
    elif interface.upper().startswith("PRE"):
        return "preconfigure"
    elif interface.upper().startswith("FIF"):
        return "FiftyGigE"
    elif interface.upper().startswith("FOU"):
        return "FourHundredGigE"
    elif interface.upper().startswith("TWO"):
        return "TwoHundredGigE"
    elif interface.upper().startswith("MG"):
        return "management"
    elif interface.upper().startswith("MA"):
        return "management"
    else:
        return "unknown"


def isipaddress(data):
    """
    Checks if the passed string is
    a valid IPv4 or IPv6 address
    """
    if not HAS_IPADDRESS:
        raise Exception(missing_required_lib("ipaddress"))
    isipaddress = True

    try:
        ipaddress.ip_address(data)
    except ValueError:
        isipaddress = False

    return isipaddress


def is_ipv4_address(data):
    """
    Checks if the passed string is
    a valid IPv4 address
    """
    if not HAS_IPADDRESS:
        raise Exception(missing_required_lib("ipaddress"))
    if "/" in data:
        data = data.split("/")[0]

    if not isipaddress(to_text(data)):
        raise ValueError("{0} is not a valid IP address".format(data))

    return ipaddress.ip_address(to_text(data)).version == 4


def prefix_to_address_wildcard(prefix):
    """Converts a IPv4 prefix into address and
        wildcard mask

    :returns: IPv4 address and wildcard mask
    """
    if not HAS_IPADDRESS:
        raise Exception(missing_required_lib("ipaddress"))
    wildcard = []

    subnet = to_text(ipaddress.IPv4Network(to_text(prefix)).netmask)

    for x in subnet.split("."):
        component = 255 - int(x)
        wildcard.append(str(component))

    wildcard = ".".join(wildcard)

    return prefix.split("/")[0], wildcard


def flatten_config(data, context):
    """Flatten different contexts in
        the running-config for easier parsing.
    :param data: dict
    :param context: str
    :returns: flattened running config
    """
    data = data.split("\n")
    in_cxt = False
    cur = {}

    for index, x in enumerate(data):
        cur_indent = len(x) - len(x.lstrip())
        if x.strip().startswith(context):
            in_cxt = True
            cur["context"] = x
            cur["indent"] = cur_indent
        elif cur and (cur_indent <= cur["indent"]):
            in_cxt = False
        elif in_cxt:
            data[index] = cur["context"] + " " + x.strip()
    return "\n".join(data)


@total_ordering
class Version:
    """Simple class to compare arbitrary versions"""

    def __init__(self, version_string):
        self.components = version_string.split(".")

    def __eq__(self, other):
        other = _coerce(other)
        if not isinstance(other, Version):
            return NotImplemented

        return self.components == other.components

    def __lt__(self, other):
        other = _coerce(other)
        if not isinstance(other, Version):
            return NotImplemented

        return self.components < other.components


def _coerce(other):
    if isinstance(other, str):
        other = Version(other)
    if isinstance(other, (int, float)):
        other = Version(str(other))
    return other


def netmask_to_cidr(netmask):
    # convert netmask to cidr and returns the cidr notation
    return str(sum([bin(int(x)).count("1") for x in netmask.split(".")]))
