"""
Support for LVS (Linux Virtual Server)
"""

import salt.utils.decorators as decorators
import salt.utils.path
from salt.exceptions import SaltException

__func_alias__ = {"list_": "list"}


# Cache the output of running which('ipvsadm')
@decorators.memoize
def __detect_os():
    return salt.utils.path.which("ipvsadm")


def __virtual__():
    """
    Only load if ipvsadm command exists on the system.
    """
    if not __detect_os():
        return (
            False,
            "The lvs execution module cannot be loaded: the ipvsadm binary is not in"
            " the path.",
        )

    return "lvs"


def _build_cmd(**kwargs):
    """

    Build a well-formatted ipvsadm command based on kwargs.
    """
    cmd = ""

    if "service_address" in kwargs:
        if kwargs["service_address"]:
            if "protocol" in kwargs:
                if kwargs["protocol"] == "tcp":
                    cmd += " -t {}".format(kwargs["service_address"])
                elif kwargs["protocol"] == "udp":
                    cmd += " -u {}".format(kwargs["service_address"])
                elif kwargs["protocol"] == "fwmark":
                    cmd += " -f {}".format(kwargs["service_address"])
                else:
                    raise SaltException(
                        "Error: Only support tcp, udp and fwmark service protocol"
                    )
                del kwargs["protocol"]
            else:
                raise SaltException("Error: protocol should specified")
            if "scheduler" in kwargs:
                if kwargs["scheduler"]:
                    cmd += " -s {}".format(kwargs["scheduler"])
                    del kwargs["scheduler"]
        else:
            raise SaltException("Error: service_address should specified")
        del kwargs["service_address"]

    if "server_address" in kwargs:
        if kwargs["server_address"]:
            cmd += " -r {}".format(kwargs["server_address"])
            if "packet_forward_method" in kwargs and kwargs["packet_forward_method"]:
                if kwargs["packet_forward_method"] == "dr":
                    cmd += " -g"
                elif kwargs["packet_forward_method"] == "tunnel":
                    cmd += " -i"
                elif kwargs["packet_forward_method"] == "nat":
                    cmd += " -m"
                else:
                    raise SaltException("Error: only support dr, tunnel and nat")
                del kwargs["packet_forward_method"]
            if "weight" in kwargs and kwargs["weight"]:
                cmd += " -w {}".format(kwargs["weight"])
                del kwargs["weight"]
        else:
            raise SaltException("Error: server_address should specified")
        del kwargs["server_address"]

    return cmd


def add_service(protocol=None, service_address=None, scheduler="wlc"):
    """
    Add a virtual service.

    protocol
        The service protocol(only support tcp, udp and fwmark service).

    service_address
        The LVS service address.

    scheduler
        Algorithm for allocating TCP connections and UDP datagrams to real servers.

    CLI Example:

    .. code-block:: bash

        salt '*' lvs.add_service tcp 1.1.1.1:80 rr
    """

    cmd = "{} -A {}".format(
        __detect_os(),
        _build_cmd(
            protocol=protocol, service_address=service_address, scheduler=scheduler
        ),
    )
    out = __salt__["cmd.run_all"](cmd, python_shell=False)

    # A non-zero return code means fail
    if out["retcode"]:
        ret = out["stderr"].strip()
    else:
        ret = True
    return ret


def edit_service(protocol=None, service_address=None, scheduler=None):
    """
    Edit the virtual service.

    protocol
        The service protocol(only support tcp, udp and fwmark service).

    service_address
        The LVS service address.

    scheduler
        Algorithm for allocating TCP connections and UDP datagrams to real servers.

    CLI Example:

    .. code-block:: bash

        salt '*' lvs.edit_service tcp 1.1.1.1:80 rr
    """

    cmd = "{} -E {}".format(
        __detect_os(),
        _build_cmd(
            protocol=protocol, service_address=service_address, scheduler=scheduler
        ),
    )
    out = __salt__["cmd.run_all"](cmd, python_shell=False)

    # A non-zero return code means fail
    if out["retcode"]:
        ret = out["stderr"].strip()
    else:
        ret = True
    return ret


def delete_service(protocol=None, service_address=None):
    """

    Delete the virtual service.

    protocol
        The service protocol(only support tcp, udp and fwmark service).

    service_address
        The LVS service address.

    CLI Example:

    .. code-block:: bash

        salt '*' lvs.delete_service tcp 1.1.1.1:80
    """

    cmd = "{} -D {}".format(
        __detect_os(), _build_cmd(protocol=protocol, service_address=service_address)
    )
    out = __salt__["cmd.run_all"](cmd, python_shell=False)

    # A non-zero return code means fail
    if out["retcode"]:
        ret = out["stderr"].strip()
    else:
        ret = True
    return ret


def add_server(
    protocol=None,
    service_address=None,
    server_address=None,
    packet_forward_method="dr",
    weight=1,
    **kwargs,
):
    """

    Add a real server to a virtual service.

    protocol
        The service protocol(only support ``tcp``, ``udp`` and ``fwmark`` service).

    service_address
        The LVS service address.

    server_address
        The real server address.

    packet_forward_method
        The LVS packet forwarding method(``dr`` for direct routing, ``tunnel`` for tunneling, ``nat`` for network access translation).

    weight
        The capacity  of a server relative to the others in the pool.

    CLI Example:

    .. code-block:: bash

        salt '*' lvs.add_server tcp 1.1.1.1:80 192.168.0.11:8080 nat 1
    """

    cmd = "{} -a {}".format(
        __detect_os(),
        _build_cmd(
            protocol=protocol,
            service_address=service_address,
            server_address=server_address,
            packet_forward_method=packet_forward_method,
            weight=weight,
            **kwargs,
        ),
    )
    out = __salt__["cmd.run_all"](cmd, python_shell=False)

    # A non-zero return code means fail
    if out["retcode"]:
        ret = out["stderr"].strip()
    else:
        ret = True
    return ret


def edit_server(
    protocol=None,
    service_address=None,
    server_address=None,
    packet_forward_method=None,
    weight=None,
    **kwargs,
):
    """

    Edit a real server to a virtual service.

    protocol
        The service protocol(only support ``tcp``, ``udp`` and ``fwmark`` service).

    service_address
        The LVS service address.

    server_address
        The real server address.

    packet_forward_method
        The LVS packet forwarding method(``dr`` for direct routing, ``tunnel`` for tunneling, ``nat`` for network access translation).

    weight
        The capacity  of a server relative to the others in the pool.

    CLI Example:

    .. code-block:: bash

        salt '*' lvs.edit_server tcp 1.1.1.1:80 192.168.0.11:8080 nat 1
    """

    cmd = "{} -e {}".format(
        __detect_os(),
        _build_cmd(
            protocol=protocol,
            service_address=service_address,
            server_address=server_address,
            packet_forward_method=packet_forward_method,
            weight=weight,
            **kwargs,
        ),
    )
    out = __salt__["cmd.run_all"](cmd, python_shell=False)

    # A non-zero return code means fail
    if out["retcode"]:
        ret = out["stderr"].strip()
    else:
        ret = True
    return ret


def delete_server(protocol=None, service_address=None, server_address=None):
    """

    Delete the realserver from the virtual service.

    protocol
        The service protocol(only support ``tcp``, ``udp`` and ``fwmark`` service).

    service_address
        The LVS service address.

    server_address
        The real server address.

    CLI Example:

    .. code-block:: bash

        salt '*' lvs.delete_server tcp 1.1.1.1:80 192.168.0.11:8080
    """

    cmd = "{} -d {}".format(
        __detect_os(),
        _build_cmd(
            protocol=protocol,
            service_address=service_address,
            server_address=server_address,
        ),
    )
    out = __salt__["cmd.run_all"](cmd, python_shell=False)

    # A non-zero return code means fail
    if out["retcode"]:
        ret = out["stderr"].strip()
    else:
        ret = True
    return ret


def clear():
    """

    Clear the virtual server table

    CLI Example:

    .. code-block:: bash

        salt '*' lvs.clear
    """

    cmd = f"{__detect_os()} -C"

    out = __salt__["cmd.run_all"](cmd, python_shell=False)

    # A non-zero return code means fail
    if out["retcode"]:
        ret = out["stderr"].strip()
    else:
        ret = True
    return ret


def get_rules():
    """

    Get the virtual server rules

    CLI Example:

    .. code-block:: bash

        salt '*' lvs.get_rules
    """

    cmd = f"{__detect_os()} -S -n"

    ret = __salt__["cmd.run"](cmd, python_shell=False)
    return ret


def list_(protocol=None, service_address=None):
    """

    List the virtual server table if service_address is not specified. If a service_address is selected, list this service only.

    CLI Example:

    .. code-block:: bash

        salt '*' lvs.list
    """

    if service_address:
        cmd = "{} -L {} -n".format(
            __detect_os(),
            _build_cmd(protocol=protocol, service_address=service_address),
        )
    else:
        cmd = f"{__detect_os()} -L -n"
    out = __salt__["cmd.run_all"](cmd, python_shell=False)

    # A non-zero return code means fail
    if out["retcode"]:
        ret = out["stderr"].strip()
    else:
        ret = out["stdout"].strip()

    return ret


def zero(protocol=None, service_address=None):
    """

    Zero the packet, byte and rate counters in a service or all services.

    CLI Example:

    .. code-block:: bash

        salt '*' lvs.zero
    """

    if service_address:
        cmd = "{} -Z {}".format(
            __detect_os(),
            _build_cmd(protocol=protocol, service_address=service_address),
        )
    else:
        cmd = f"{__detect_os()} -Z"
    out = __salt__["cmd.run_all"](cmd, python_shell=False)

    # A non-zero return code means fail
    if out["retcode"]:
        ret = out["stderr"].strip()
    else:
        ret = True
    return ret


def check_service(protocol=None, service_address=None, **kwargs):
    """

    Check the virtual service exists.

    CLI Example:

    .. code-block:: bash

        salt '*' lvs.check_service tcp 1.1.1.1:80
    """

    cmd = "{}".format(
        _build_cmd(protocol=protocol, service_address=service_address, **kwargs)
    )
    # Exact match
    if not kwargs:
        cmd += " "

    all_rules = get_rules()
    out = all_rules.find(cmd)

    if out != -1:
        ret = True
    else:
        ret = "Error: service not exists"
    return ret


def check_server(protocol=None, service_address=None, server_address=None, **kwargs):
    """

    Check the real server exists in the specified service.

    CLI Example:

    .. code-block:: bash

         salt '*' lvs.check_server tcp 1.1.1.1:80 192.168.0.11:8080
    """

    cmd = "{}".format(
        _build_cmd(
            protocol=protocol,
            service_address=service_address,
            server_address=server_address,
            **kwargs,
        )
    )
    # Exact match
    if not kwargs:
        cmd += " "

    all_rules = get_rules()
    out = all_rules.find(cmd)

    if out != -1:
        ret = True
    else:
        ret = "Error: server not exists"
    return ret
