"""
"""

import json
import os
import sys
import warnings
from logging import getLogger
from pathlib import Path
from typing import Any, Callable, List, Optional, Tuple, Union

from .platforms import Menu, MenuItem
from .utils import DEFAULT_BASE_PREFIX, DEFAULT_PREFIX, _UserOrSystem, elevate_as_needed

log = getLogger(__name__)


__all__ = [
    "install",
    "remove",
    "install_all",
    "remove_all",
]


def _load(
    metadata_or_path: Union[os.PathLike, dict],
    target_prefix: Optional[os.PathLike] = None,
    base_prefix: Optional[os.PathLike] = None,
    _mode: _UserOrSystem = "user",
) -> Tuple[Menu, List[MenuItem]]:
    target_prefix = target_prefix or DEFAULT_PREFIX
    base_prefix = base_prefix or DEFAULT_BASE_PREFIX
    if isinstance(metadata_or_path, (str, Path)):
        with open(metadata_or_path) as f:
            metadata = json.load(f)
    else:
        metadata = metadata_or_path
    menu = Menu(metadata["menu_name"], target_prefix, base_prefix, _mode)
    menu_items = [MenuItem(menu, item) for item in metadata["menu_items"]]
    return menu, menu_items


@elevate_as_needed
def install(
    metadata_or_path: Union[os.PathLike, dict],
    *,
    target_prefix: Optional[os.PathLike] = None,
    base_prefix: Optional[os.PathLike] = None,
    _mode: _UserOrSystem = "user",
) -> List[os.PathLike]:
    target_prefix = target_prefix or DEFAULT_PREFIX
    base_prefix = base_prefix or DEFAULT_BASE_PREFIX
    menu, menu_items = _load(metadata_or_path, target_prefix, base_prefix, _mode)
    if not any(item.enabled_for_platform() for item in menu_items):
        warnings.warn(f"Metadata for {menu.name} is not enabled for {sys.platform}")
        return ()

    paths = []
    paths += menu.create()
    for menu_item in menu_items:
        paths += menu_item.create()

    return paths


@elevate_as_needed
def remove(
    metadata_or_path: Union[os.PathLike, dict],
    *,
    target_prefix: Optional[os.PathLike] = None,
    base_prefix: Optional[os.PathLike] = None,
    _mode: _UserOrSystem = "user",
) -> List[os.PathLike]:
    target_prefix = target_prefix or DEFAULT_PREFIX
    base_prefix = base_prefix or DEFAULT_BASE_PREFIX
    menu, menu_items = _load(metadata_or_path, target_prefix, base_prefix, _mode)
    if not any(item.enabled_for_platform() for item in menu_items):
        warnings.warn(f"Metadata for {menu.name} is not enabled for {sys.platform}")
        return ()

    paths = []
    for menu_item in menu_items:
        paths += menu_item.remove()
    paths += menu.remove()

    return paths


@elevate_as_needed
def install_all(
    *,
    target_prefix: Optional[os.PathLike] = None,
    base_prefix: Optional[os.PathLike] = None,
    filter: Union[Callable, None] = None,
    _mode: _UserOrSystem = "user",
) -> List[List[os.PathLike]]:
    target_prefix = target_prefix or DEFAULT_PREFIX
    base_prefix = base_prefix or DEFAULT_BASE_PREFIX
    return _process_all(install, target_prefix, base_prefix, filter, _mode)


@elevate_as_needed
def remove_all(
    *,
    target_prefix: Optional[os.PathLike] = None,
    base_prefix: Optional[os.PathLike] = None,
    filter: Union[Callable, None] = None,
    _mode: _UserOrSystem = "user",
) -> List[List[Union[str, os.PathLike]]]:
    target_prefix = target_prefix or DEFAULT_PREFIX
    base_prefix = base_prefix or DEFAULT_BASE_PREFIX
    return _process_all(remove, target_prefix, base_prefix, filter, _mode)


def _process_all(
    function: Callable,
    target_prefix: Optional[os.PathLike] = None,
    base_prefix: Optional[os.PathLike] = None,
    filter: Union[Callable, None] = None,
    _mode: _UserOrSystem = "user",
) -> List[Any]:
    target_prefix = target_prefix or DEFAULT_PREFIX
    base_prefix = base_prefix or DEFAULT_BASE_PREFIX
    jsons = (Path(target_prefix) / "Menu").glob("*.json")
    results = []
    for path in jsons:
        if filter is not None and filter(path):
            results.append(function(path, target_prefix, base_prefix, _mode))
    return results


_api_remove = remove  # alias to prevent shadowing in the function below


def _install_adapter(
    path: os.PathLike, remove: bool = False, prefix: os.PathLike = DEFAULT_PREFIX, **kwargs
):
    """
    This function is only here as a legacy adapter for menuinst v1.x.
    Please use `menuinst.api` functions instead.
    """
    if os.name == "nt":
        path = path.replace("/", "\\")
    json_path = os.path.join(prefix, path)
    with open(json_path) as f:
        metadata = json.load(f)
    if "$id" not in metadata:  # old style JSON
        from ._legacy import install as _legacy_install

        if os.name == "nt":
            kwargs.setdefault("root_prefix", kwargs.pop("base_prefix", DEFAULT_BASE_PREFIX))
            if kwargs["root_prefix"] is None:
                kwargs["root_prefix"] = DEFAULT_BASE_PREFIX
            _legacy_install(json_path, remove=remove, prefix=prefix, **kwargs)
        else:
            log.warn(
                "menuinst._legacy is only supported on Windows. "
                "Switch to the new-style menu definitions "
                "for cross-platform compatibility."
            )
    else:
        # patch kwargs to reroute root_prefix to base_prefix
        kwargs.setdefault("base_prefix", kwargs.pop("root_prefix", DEFAULT_BASE_PREFIX))
        if kwargs["base_prefix"] is None:
            kwargs["base_prefix"] = DEFAULT_BASE_PREFIX
        if remove:
            _api_remove(metadata, target_prefix=prefix, **kwargs)
        else:
            install(metadata, target_prefix=prefix, **kwargs)
