"""
Execution module for creating shortcuts on Windows. Handles file shortcuts
(`.lnk`) and url shortcuts (`.url`). Allows for the configuration of icons and
hot keys on file shortcuts. Changing the icon and hot keys are unsupported for
url shortcuts.

.. versionadded:: 3005
"""

# https://docs.microsoft.com/en-us/troubleshoot/windows-client/admin-development/create-desktop-shortcut-with-wsh
# https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/windows-scripting/f5y78918(v=vs.84)
import logging
import os
import time

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

HAS_WIN32 = False
if salt.utils.platform.is_windows():
    import win32com.client

    HAS_WIN32 = True

# Set up logging
log = logging.getLogger(__name__)

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


WINDOW_STYLE = {
    1: "Normal",
    3: "Maximized",
    7: "Minimized",
    "Normal": 1,
    "Maximized": 3,
    "Minimized": 7,
}


def __virtual__():
    """
    Make sure we're on Windows
    """
    # Verify Windows
    if not salt.utils.platform.is_windows():
        log.debug("Shortcut module only available on Windows systems")
        return False, "Shortcut module only available on Windows systems"
    if not HAS_WIN32:
        log.debug("Shortcut module requires pywin32")
        return False, "Shortcut module requires pywin32"

    return __virtualname__


def get(path):
    r"""
    Gets the properties for a shortcut

    Args:

        path (str):
            The path to the shortcut. Must have a `.lnk` or `.url` file
            extension.

    Returns:
        dict: A dictionary containing all available properties for the specified
            shortcut

    CLI Example:

    .. code-block:: bash

        salt '*' shortcut.get path='C:\path\to\shortcut.lnk'
    """
    if not os.path.exists(path):
        raise CommandExecutionError(f"Shortcut not found: {path}")

    if not path.endswith((".lnk", ".url")):
        _, ext = os.path.splitext(path)
        raise CommandExecutionError(f"Invalid file extension: {ext}")

    # This will load the existing shortcut
    with salt.utils.winapi.Com():
        shell = win32com.client.Dispatch(  # pylint: disable=used-before-assignment
            "WScript.Shell"
        )
        shortcut = shell.CreateShortcut(path)

        arguments = ""
        description = ""
        hot_key = ""
        icon_location = ""
        icon_index = 0
        window_style = ""
        working_dir = ""

        path = salt.utils.path.expand(shortcut.FullName)
        # A shortcut can have either a .lnk or a .url extension. We only want to
        # expand the target if it is a .lnk
        if path.endswith(".lnk"):
            target = shortcut.TargetPath
            if target:
                target = salt.utils.path.expand(target)
            else:
                msg = f"Not a valid shortcut: {path}"
                log.debug(msg)
                raise CommandExecutionError(msg)
            if shortcut.Arguments:
                arguments = shortcut.Arguments
            if shortcut.Description:
                description = shortcut.Description
            if shortcut.Hotkey:
                hot_key = shortcut.Hotkey
            if shortcut.IconLocation:
                icon_location, icon_index = shortcut.IconLocation.split(",")
                if icon_location:
                    icon_location = salt.utils.path.expand(icon_location)
            if shortcut.WindowStyle:
                window_style = WINDOW_STYLE[shortcut.WindowStyle]
            if shortcut.WorkingDirectory:
                working_dir = salt.utils.path.expand(shortcut.WorkingDirectory)
        else:
            target = shortcut.TargetPath

        return {
            "arguments": arguments,
            "description": description,
            "hot_key": hot_key,
            "icon_index": int(icon_index),
            "icon_location": icon_location,
            "path": path,
            "target": target,
            "window_style": window_style,
            "working_dir": working_dir,
        }


def _set_info(
    path,
    target="",
    arguments="",
    description="",
    hot_key="",
    icon_index=0,
    icon_location="",
    window_style="Normal",
    working_dir="",
):
    r"""
    The main worker function for creating and modifying shortcuts. the `create`
    and `modify` functions are wrappers around this function.

    Args:

        path (str): The full path to the shortcut.

        target (:obj:`str`, optional): The full path to the target.

            Defaults is "".

        arguments (:obj:`str`, optional):
            Any arguments to be passed to the target.

            Default is "".

        description (:obj:`str`, optional):
            The description for the shortcut. This is shown in the ``Comment``
            field of the dialog box.

            Default is "".

        hot_key (:obj:`str`, optional):
            A combination of hot Keys to trigger this shortcut. This is
            something like ``Ctrl+Alt+D``. This is shown in the ``Shortcut key``
            field in the dialog box. Available options are:

            - Ctrl
            - Alt
            - Shift
            - Ext

            Default is "".

        icon_index (:obj:`int`, optional):
            The index for the icon to use in files that contain multiple icons.

            Default is 0.

        icon_location (:obj`str`, optional):
            The full path to a file containing icons. This is shown in the
            ``Change Icon`` dialog box by clicking the ``Change Icon`` button.
            If no file is specified and a binary is passed as the target,
            Windows will attempt to get the icon from the binary file.

            Default is "".

        window_style (:obj:`str`, optional):
            The window style the program should start in. This is shown in the
            ``Run`` field of the dialog box. Valid options are:

            - Normal
            - Minimized
            - Maximized

            Default is "Normal".

        working_dir (:obj:`str`, optional):
            The full path to the working directory for the program to run in.
            This is shown in the ``Start in`` field of the dialog box.

            Default is "".

    Returns:
        bool: ``True`` if successful.
    """
    path = salt.utils.path.expand(path)

    # This will load the existing shortcut if it already exists
    # If it is a new shortcut, it won't be created until it is saved
    with salt.utils.winapi.Com():
        shell = win32com.client.Dispatch("WScript.Shell")
        shortcut = shell.CreateShortcut(path)

        # A shortcut can have either a .lnk or a .url extension. We only want to
        # expand the target if it is a .lnk
        if path.endswith(".lnk"):
            if target:
                target = salt.utils.path.expand(target)

            # These settings only apply to lnk shortcuts
            if arguments:
                shortcut.Arguments = arguments
            if description:
                shortcut.Description = description
            if hot_key:
                shortcut.Hotkey = hot_key
            if icon_location:
                shortcut.IconLocation = ",".join([icon_location, str(icon_index)])
            if window_style:
                shortcut.WindowStyle = WINDOW_STYLE[window_style]
            if working_dir:
                shortcut.WorkingDirectory = working_dir

        shortcut.TargetPath = target

        shortcut.Save()
    return True


def modify(
    path,
    target="",
    arguments="",
    description="",
    hot_key="",
    icon_index=0,
    icon_location="",
    window_style="Normal",
    working_dir="",
):
    r"""
    Modify an existing shortcut. This can be a file shortcut (``.lnk``) or a
    url shortcut (``.url``).

    Args:

        path (str): The full path to the shortcut. Must have a `.lnk` or `.url`
            file extension.

        target (:obj:`str`, optional): The full path to the target.

            Default is "".

        arguments (:obj:`str`, optional):
            Any arguments to be passed to the target.

            Default is "".

        description (:obj:`str`, optional):
            The description for the shortcut. This is shown in the ``Comment``
            field of the dialog box.

            Default is "".

        hot_key (:obj:`str`, optional):
            A combination of hot Keys to trigger this shortcut. This is
            something like ``Ctrl+Alt+D``. This is shown in the ``Shortcut key``
            field in the dialog box. Available options are:

            - Ctrl
            - Alt
            - Shift
            - Ext

            Default is "".

        icon_index (:obj:`int`, optional):
            The index for the icon to use in files that contain multiple icons.

            Default is 0.

        icon_location (:obj:`str`, optional):
            The full path to a file containing icons. This is shown in the
            ``Change Icon`` dialog box by clicking the ``Change Icon`` button.
            If no file is specified and a binary is passed as the target,
            Windows will attempt to get the icon from the binary file.

            Default is "".

        window_style (:obj:`str`, optional):
            The window style the program should start in. This is shown in the
            ``Run`` field of the dialog box. Valid options are:

            - Normal
            - Minimized
            - Maximized

            Default is "Normal".

        working_dir (:obj:`str`, optional):
            The full path to the working directory for the program to run in.
            This is shown in the ``Start in`` field of the dialog box.

            Default is "".

    Returns:
        bool: ``True`` if successful.

    CLI Example:

    .. code-block:: bash

        # Modify an existing shortcut. Set it to target notepad.exe
        salt '*' shortcut.modify 'C:\path\to\shortcut.lnk' 'C:\Windows\notepad.exe'
    """
    if not os.path.exists(path):
        raise CommandExecutionError(f"Shortcut not found: {path}")

    if not path.endswith((".lnk", ".url")):
        _, ext = os.path.splitext(path)
        raise CommandExecutionError(f"Invalid file extension: {ext}")

    return _set_info(
        path=path,
        arguments=arguments,
        description=description,
        hot_key=hot_key,
        icon_index=icon_index,
        icon_location=icon_location,
        target=target,
        window_style=window_style,
        working_dir=working_dir,
    )


def create(
    path,
    target,
    arguments="",
    description="",
    hot_key="",
    icon_index=0,
    icon_location="",
    window_style="Normal",
    working_dir="",
    backup=False,
    force=False,
    make_dirs=False,
    user=None,
):
    r"""
    Create a new shortcut. This can be a file shortcut (``.lnk``) or a url
    shortcut (``.url``).

    Args:

        path (str):
            The full path to the shortcut. Must have a `.lnk` or `.url` file
            extension.

        target (str): The full path to the target.

        arguments (:obj:`str`, optional):
            Any arguments to be passed to the target.

            Default is "".

        description (:obj:`str`, optional):
            The description for the shortcut. This is shown in the ``Comment``
            field of the dialog box.

            Default is "".

        hot_key (:obj:`str`, optional):
            A combination of hot Keys to trigger this shortcut. This is
            something like ``Ctrl+Alt+D``. This is shown in the ``Shortcut key``
            field in the dialog box. Available options are:

            - Ctrl
            - Alt
            - Shift
            - Ext

            Default is "".

        icon_index (:obj:`int`, optional):
            The index for the icon to use in files that contain multiple icons.

            Default is 0.

        icon_location (:obj:`str`, optional):
            The full path to a file containing icons. This is shown in the
            ``Change Icon`` dialog box by clicking the ``Change Icon`` button.
            If no file is specified and a binary is passed as the target,
            Windows will attempt to get the icon from the binary file.

            Default is "".

        window_style (:obj:`str`, optional):
            The window style the program should start in. This is shown in the
            ``Run`` field of the dialog box. Valid options are:

            - Normal
            - Minimized
            - Maximized

            Default is "Normal".

        working_dir (:obj:`str`, optional):
            The full path to the working directory for the program to run in.
            This is shown in the ``Start in`` field of the dialog box.

            Default is "".

        backup (:obj:`bool`, optional):
            If there is already a shortcut with the same name, set this value to
            ``True`` to backup the existing shortcut and continue creating the
            new shortcut.

            Default is ``False``.

        force (:obj:`bool`, optional):
            If there is already a shortcut with the same name and you aren't
            backing up the shortcut, set this value to ``True`` to remove the
            existing shortcut and create a new with these settings.

            Default is ``False``.

        make_dirs (:obj:`bool`, optional):
            If the parent directory structure does not exist for the new
            shortcut, create it.

            Default is ``False``.

        user (:obj:`str`, optional):
            The user to be the owner of any directories created by setting
            ``make_dirs`` to ``True``. If no value is passed, Salt will use the
            user account that it is running under.

            Default is "".

    Returns:
        bool: ``True`` if successful.

    Raises:
        CommandExecutionError: If the path is not a ``.lnk`` or ``.url`` file
            extension.
        CommandExecutionError: If there is an existing shortcut with the same
            name and ``backup`` and ``force`` are both ``False``
        CommandExecutionError: If the parent directory is not created and
            ``make_dirs`` is ``False``
        CommandExecutionError: If there was an error creating the parent
            directories

    CLI Example:

    .. code-block:: bash

        # Create a shortcut and set the ``Shortcut key`` (``hot_key``)
        salt '*' shortcut.create 'C:\path\to\shortcut.lnk' 'C:\Windows\notepad.exe' hot_key='Ctrl+Alt+N'

        # Create a shortcut and change the icon to the 3rd one in the icon file
        salt '*' shortcut.create 'C:\path\to\shortcut.lnk' 'C:\Windows\notepad.exe' icon_location='C:\path\to\icon.ico' icon_index=2

        # Create a shortcut and change the startup mode to full screen
        salt '*' shortcut.create 'C:\path\to\shortcut.lnk' 'C:\Windows\notepad.exe' window_style='Maximized'

        # Create a shortcut and change the icon
        salt '*' shortcut.create 'C:\path\to\shortcut.lnk' 'C:\Windows\notepad.exe' icon_location='C:\path\to\icon.ico'

        # Create a shortcut and force it to overwrite an existing shortcut
        salt '*' shortcut.create 'C:\path\to\shortcut.lnk' 'C:\Windows\notepad.exe' force=True

        # Create a shortcut and create any parent directories if they are missing
        salt '*' shortcut.create 'C:\path\to\shortcut.lnk' 'C:\Windows\notepad.exe' make_dirs=True
    """
    if not path.endswith((".lnk", ".url")):
        _, ext = os.path.splitext(path)
        raise CommandExecutionError(f"Invalid file extension: {ext}")

    if os.path.exists(path):
        if backup:
            log.debug("Backing up: %s", path)
            file, ext = os.path.splitext(path)
            ext = ext.strip(".")
            backup_path = f"{file}-{time.time_ns()}.{ext}"
            os.rename(path, backup_path)
        elif force:
            log.debug("Removing: %s", path)
            os.remove(path)
        else:
            log.debug("Shortcut exists: %s", path)
            raise CommandExecutionError("Found existing shortcut")

    if not os.path.isdir(os.path.dirname(path)):
        if make_dirs:

            # Get user from opts if not defined
            if not user:
                user = __opts__["user"]

            # Make sure the user exists in Windows
            # Salt default is 'SYSTEM' for Windows
            if not __salt__["user.info"](user):
                # User not found, use the account salt is running under
                # If username not found, use System
                user = __salt__["user.current"]()
                if not user:
                    user = "SYSTEM"

            try:
                __salt__["file.makedirs"](path=path, owner=user)
            except CommandExecutionError as exc:
                raise CommandExecutionError(
                    f"Error creating parent directory: {exc.message}"
                )
        else:
            raise CommandExecutionError(
                f"Parent directory not present: {os.path.dirname(path)}"
            )

    return _set_info(
        path=path,
        arguments=arguments,
        description=description,
        hot_key=hot_key,
        icon_index=icon_index,
        icon_location=icon_location,
        target=target,
        window_style=window_style,
        working_dir=working_dir,
    )
