"""
Support for Linux File Access Control Lists

The Linux ACL module requires the `getfacl` and `setfacl` binaries.

"""

import salt.utils.path
from salt.exceptions import CommandExecutionError

# Define the module's virtual name
__virtualname__ = "acl"


def __virtual__():
    """
    Only load the module if getfacl is installed
    """
    if salt.utils.path.which("getfacl"):
        return __virtualname__
    return (
        False,
        "The linux_acl execution module cannot be loaded: the getfacl binary is not in"
        " the path.",
    )


def version():
    """
    Return facl version from getfacl --version

    CLI Example:

    .. code-block:: bash

        salt '*' acl.version
    """
    cmd = "getfacl --version"
    out = __salt__["cmd.run"](cmd).splitlines()
    ret = out[0].split()
    return ret[1].strip()


def _raise_on_no_files(*args):
    if not args:
        raise CommandExecutionError(
            "You need to specify at least one file or directory to work with!"
        )


def getfacl(*args, **kwargs):
    """
    Return (extremely verbose) map of FACLs on specified file(s)

    CLI Examples:

    .. code-block:: bash

        salt '*' acl.getfacl /tmp/house/kitchen
        salt '*' acl.getfacl /tmp/house/kitchen /tmp/house/livingroom
        salt '*' acl.getfacl /tmp/house/kitchen /tmp/house/livingroom recursive=True
    """
    recursive = kwargs.pop("recursive", False)

    _raise_on_no_files(*args)

    ret = {}
    cmd = "getfacl --absolute-names"
    if recursive:
        cmd += " -R"
    for dentry in args:
        cmd += f' "{dentry}"'
    out = __salt__["cmd.run"](cmd, python_shell=False).splitlines()
    dentry = ""
    for line in out:
        if not line:
            continue
        elif line.startswith("getfacl"):
            continue
        elif line.startswith("#"):
            comps = line.replace("# ", "").split(": ")
            if comps[0] == "file":
                dentry = comps[1]
                ret[dentry] = {"comment": {}, "user": [], "group": []}
            ret[dentry]["comment"][comps[0]] = comps[1]
            if comps[0] == "flags":
                flags = list(comps[1])
                if flags[0] == "s":
                    ret[dentry]["suid"] = True
                if flags[1] == "s":
                    ret[dentry]["sgid"] = True
                if flags[2] == "t":
                    ret[dentry]["sticky"] = True
        else:
            vals = _parse_acl(
                acl=line,
                user=ret[dentry]["comment"]["owner"],
                group=ret[dentry]["comment"]["group"],
            )
            acl_type = vals["type"]
            del vals["type"]
            for entity in ("user", "group"):
                if entity in vals:
                    usergroup = vals[entity]
                    del vals[entity]
                    if acl_type == "acl":
                        ret[dentry][entity].append({usergroup: vals})
                    elif acl_type == "default":
                        if "defaults" not in ret[dentry]:
                            ret[dentry]["defaults"] = {}
                        if entity not in ret[dentry]["defaults"]:
                            ret[dentry]["defaults"][entity] = []
                        ret[dentry]["defaults"][entity].append({usergroup: vals})
            for entity in ("other", "mask"):
                if entity in vals:
                    del vals[entity]
                    if acl_type == "acl":
                        ret[dentry][entity] = [{"": vals}]
                    elif acl_type == "default":
                        if "defaults" not in ret[dentry]:
                            ret[dentry]["defaults"] = {}
                        ret[dentry]["defaults"][entity] = [{"": vals}]

    return ret


def _parse_acl(acl, user, group):
    """
    Parse a single ACL rule
    """
    comps = acl.split(":")
    vals = {}

    # What type of rule is this?
    vals["type"] = "acl"
    if comps[0] == "default":
        vals["type"] = "default"
        comps.pop(0)

    # If a user is not specified, use the owner of the file
    if comps[0] == "user" and not comps[1]:
        comps[1] = user
    elif comps[0] == "group" and not comps[1]:
        comps[1] = group
    vals[comps[0]] = comps[1]

    # Set the permissions fields
    octal = 0
    vals["permissions"] = {}
    if "r" in comps[-1]:
        octal += 4
        vals["permissions"]["read"] = True
    else:
        vals["permissions"]["read"] = False
    if "w" in comps[-1]:
        octal += 2
        vals["permissions"]["write"] = True
    else:
        vals["permissions"]["write"] = False
    if "x" in comps[-1]:
        octal += 1
        vals["permissions"]["execute"] = True
    else:
        vals["permissions"]["execute"] = False
    vals["octal"] = octal

    return vals


def wipefacls(*args, **kwargs):
    """
    Remove all FACLs from the specified file(s)

    CLI Examples:

    .. code-block:: bash

        salt '*' acl.wipefacls /tmp/house/kitchen
        salt '*' acl.wipefacls /tmp/house/kitchen /tmp/house/livingroom
        salt '*' acl.wipefacls /tmp/house/kitchen /tmp/house/livingroom recursive=True
    """
    recursive = kwargs.pop("recursive", False)

    _raise_on_no_files(*args)
    cmd = "setfacl -b"
    if recursive:
        cmd += " -R"
    for dentry in args:
        cmd += f' "{dentry}"'
    __salt__["cmd.run"](cmd, python_shell=False)
    return True


def _acl_prefix(acl_type):
    prefix = ""
    if acl_type.startswith("d"):
        prefix = "d:"
        acl_type = acl_type.replace("default:", "")
        acl_type = acl_type.replace("d:", "")
    if acl_type == "user" or acl_type == "u":
        prefix += "u"
    elif acl_type == "group" or acl_type == "g":
        prefix += "g"
    elif acl_type == "mask" or acl_type == "m":
        prefix += "m"
    return prefix


def modfacl(acl_type, acl_name="", perms="", *args, **kwargs):
    """
    Add or modify a FACL for the specified file(s)

    CLI Examples:

    .. code-block:: bash

        salt '*' acl.modfacl user myuser rwx /tmp/house/kitchen
        salt '*' acl.modfacl default:group mygroup rx /tmp/house/kitchen
        salt '*' acl.modfacl d:u myuser 7 /tmp/house/kitchen
        salt '*' acl.modfacl g mygroup 0 /tmp/house/kitchen /tmp/house/livingroom
        salt '*' acl.modfacl user myuser rwx /tmp/house/kitchen recursive=True
        salt '*' acl.modfacl user myuser rwx /tmp/house/kitchen raise_err=True
    """
    recursive = kwargs.pop("recursive", False)
    raise_err = kwargs.pop("raise_err", False)

    _raise_on_no_files(*args)

    cmd = "setfacl"
    if recursive:
        # -R must come first as -m needs the acl_* arguments that come later
        cmd += " -R"

    cmd += " -m"

    cmd = f"{cmd} {_acl_prefix(acl_type)}:{acl_name}:{perms}"

    for dentry in args:
        cmd += f' "{dentry}"'
    __salt__["cmd.run"](cmd, python_shell=False, raise_err=raise_err)
    return True


def delfacl(acl_type, acl_name="", *args, **kwargs):
    """
    Remove specific FACL from the specified file(s)

    CLI Examples:

    .. code-block:: bash

        salt '*' acl.delfacl user myuser /tmp/house/kitchen
        salt '*' acl.delfacl default:group mygroup /tmp/house/kitchen
        salt '*' acl.delfacl d:u myuser /tmp/house/kitchen
        salt '*' acl.delfacl g myuser /tmp/house/kitchen /tmp/house/livingroom
        salt '*' acl.delfacl user myuser /tmp/house/kitchen recursive=True
    """
    recursive = kwargs.pop("recursive", False)

    _raise_on_no_files(*args)

    cmd = "setfacl"
    if recursive:
        cmd += " -R"

    cmd += " -x"

    cmd = f"{cmd} {_acl_prefix(acl_type)}:{acl_name}"

    for dentry in args:
        cmd += f' "{dentry}"'
    __salt__["cmd.run"](cmd, python_shell=False)
    return True
