"""
This module allows you to manage assistive access on macOS minions with 10.9+

.. versionadded:: 2016.3.0

.. code-block:: bash

    salt '*' assistive.install /usr/bin/osascript
"""

import logging
import re

import salt.utils.platform
import salt.utils.stringutils
from salt.exceptions import CommandExecutionError
from salt.utils.versions import LooseVersion as _LooseVersion

log = logging.getLogger(__name__)

__virtualname__ = "assistive"


def __virtual__():
    """
    Only work on Mac OS
    """
    if not salt.utils.platform.is_darwin():
        return False, "Must be run on macOS"
    if _LooseVersion(__grains__["osrelease"]) < salt.utils.stringutils.to_str("10.9"):
        return False, "Must be run on macOS 10.9 or newer"
    return __virtualname__


def install(app_id, enable=True):
    """
    Install a bundle ID or command as being allowed to use
    assistive access.

    app_id
        The bundle ID or command to install for assistive access.

    enabled
        Sets enabled or disabled status. Default is ``True``.

    CLI Example:

    .. code-block:: bash

        salt '*' assistive.install /usr/bin/osascript
        salt '*' assistive.install com.smileonmymac.textexpander
    """
    ge_el_capitan = (
        True
        if _LooseVersion(__grains__["osrelease"])
        >= salt.utils.stringutils.to_str("10.11")
        else False
    )
    ge_mojave = (
        True
        if _LooseVersion(__grains__["osrelease"])
        >= salt.utils.stringutils.to_str("10.14")
        else False
    )
    client_type = _client_type(app_id)
    enable_str = "1" if enable else "0"
    cmd = (
        'sqlite3 "/Library/Application Support/com.apple.TCC/TCC.db" "INSERT or'
        " REPLACE INTO access"
        " VALUES('kTCCServiceAccessibility','{}',{},{},1,NULL{}{})\"".format(
            app_id,
            client_type,
            enable_str,
            ",NULL" if ge_el_capitan else "",
            ",NULL,NULL,NULL,NULL,''" if ge_mojave else "",
        )
    )

    call = __salt__["cmd.run_all"](cmd, output_loglevel="debug", python_shell=False)
    if call["retcode"] != 0:
        comment = ""
        if "stderr" in call:
            comment += call["stderr"]
        if "stdout" in call:
            comment += call["stdout"]

        raise CommandExecutionError("Error installing app: {}".format(comment))

    return True


def installed(app_id):
    """
    Check if a bundle ID or command is listed in assistive access.
    This will not check to see if it's enabled.

    app_id
        The bundle ID or command to check installed status.

    CLI Example:

    .. code-block:: bash

        salt '*' assistive.installed /usr/bin/osascript
        salt '*' assistive.installed com.smileonmymac.textexpander
    """
    for a in _get_assistive_access():
        if app_id == a[0]:
            return True

    return False


def enable(app_id, enabled=True):
    """
    Enable or disable an existing assistive access application.

    app_id
        The bundle ID or command to set assistive access status.

    enabled
        Sets enabled or disabled status. Default is ``True``.

    CLI Example:

    .. code-block:: bash

        salt '*' assistive.enable /usr/bin/osascript
        salt '*' assistive.enable com.smileonmymac.textexpander enabled=False
    """
    enable_str = "1" if enabled else "0"
    for a in _get_assistive_access():
        if app_id == a[0]:
            cmd = (
                'sqlite3 "/Library/Application Support/com.apple.TCC/TCC.db" '
                "\"UPDATE access SET allowed='{}' WHERE client='{}'\"".format(
                    enable_str, app_id
                )
            )

            call = __salt__["cmd.run_all"](
                cmd, output_loglevel="debug", python_shell=False
            )

            if call["retcode"] != 0:
                comment = ""
                if "stderr" in call:
                    comment += call["stderr"]
                if "stdout" in call:
                    comment += call["stdout"]

                raise CommandExecutionError("Error enabling app: {}".format(comment))

            return True

    return False


def enabled(app_id):
    """
    Check if a bundle ID or command is listed in assistive access and
    enabled.

    app_id
        The bundle ID or command to retrieve assistive access status.

    CLI Example:

    .. code-block:: bash

        salt '*' assistive.enabled /usr/bin/osascript
        salt '*' assistive.enabled com.smileonmymac.textexpander
    """
    for a in _get_assistive_access():
        if app_id == a[0] and a[1] == "1":
            return True

    return False


def remove(app_id):
    """
    Remove a bundle ID or command as being allowed to use assistive access.

    app_id
        The bundle ID or command to remove from assistive access list.

    CLI Example:

    .. code-block:: bash

        salt '*' assistive.remove /usr/bin/osascript
        salt '*' assistive.remove com.smileonmymac.textexpander
    """
    cmd = (
        'sqlite3 "/Library/Application Support/com.apple.TCC/TCC.db" '
        "\"DELETE from access where client='{}'\"".format(app_id)
    )
    call = __salt__["cmd.run_all"](cmd, output_loglevel="debug", python_shell=False)

    if call["retcode"] != 0:
        comment = ""
        if "stderr" in call:
            comment += call["stderr"]
        if "stdout" in call:
            comment += call["stdout"]

        raise CommandExecutionError("Error removing app: {}".format(comment))

    return True


def _client_type(app_id):
    """
    Determine whether the given ID is a bundle ID or a
    a path to a command
    """
    return "1" if app_id[0] == "/" else "0"


def _get_assistive_access():
    """
    Get a list of all of the assistive access applications installed,
    returns as a ternary showing whether each app is enabled or not.
    """
    cmd = (
        'sqlite3 "/Library/Application Support/com.apple.TCC/TCC.db" "SELECT * FROM'
        ' access"'
    )
    call = __salt__["cmd.run_all"](cmd, output_loglevel="debug", python_shell=False)

    if call["retcode"] != 0:
        comment = ""
        if "stderr" in call:
            comment += call["stderr"]
        if "stdout" in call:
            comment += call["stdout"]

        raise CommandExecutionError("Error: {}".format(comment))

    out = call["stdout"]
    return re.findall(
        r"kTCCServiceAccessibility\|(.*)\|[0-9]{1}\|([0-9]{1})\|[0-9]{1}\|",
        out,
        re.MULTILINE,
    )
