"""directories is used to generate the necessary directory structures."""
from logging import INFO, Logger
from os import PathLike
from pathlib import Path
from typing import Iterable, MutableSequence, Set

from ansible_generator.log import setup_logger
from ansible_generator.utilities import join_cwd_and_directory_path

StrPath = str | PathLike[str]


def create_directory_layout(
    projects: Iterable[str],
    inventories: MutableSequence[str],
    alternate_layout: bool = False,
    verbosity: int = INFO,
) -> bool:
    """Creates the directory layout.

    Args:
        inventories: Array of strings noting the names of the inventories.
        alternate_layout: Boolean noting whether this is the primary or
            alternate directory structure.

    Returns:
        A boolean to say that it succeeded or failed.
    """
    logger = setup_logger(name=__name__, log_level=verbosity)
    for itemNum, inventory in enumerate(inventories):
        inventories[itemNum] = inventory.split("/")[-1]
        if inventory == ".":
            inventories[itemNum] = "dot"
        if inventory == "..":
            inventories[itemNum] = "dotdot"
        if inventory == "*":
            inventories[itemNum] = "star"

    if alternate_layout:
        required_paths = get_alternate_inventories_directory_paths(
            logger=logger, inventories=inventories
        )
    else:
        required_paths = {"group_vars", "host_vars", "roles"}

    logger.debug(
        'msg="%s required directories" directories="%s"',
        len(required_paths),
        ", ".join(required_paths),
    )
    if projects:
        logger.debug('msg="projects was defined" projects="%s"', ", ".join(projects))

        final_paths: Set[str] = set()
        for project in projects:
            final_paths.update(
                f"{project}/{required_path}" for required_path in required_paths
            )
        required_paths = final_paths
        logger.debug(
            'msg="%s project required directories" directories="%s"',
            len(required_paths),
            ", ".join(required_paths),
        )

    create_paths = set(map(join_cwd_and_directory_path, required_paths))
    for cp in create_paths:
        success = create_directory(logger=logger, dir_path=cp)
        if not success:
            return False

    return True


def create_directory(logger: Logger, dir_path: StrPath) -> bool:
    """Recursively creates a directory path if does not exist.

    Args:
        dir_path: The path to the directory that we would like created.

    Returns:
        A boolean to say that it was successful (True) or it failed (False).
    """
    if not isinstance(dir_path, Path):
        dir_path = Path(dir_path)

    if dir_path.exists():
        logger.info("directory %s exists", dir_path)
        return True

    try:
        logger.info("creating directory %s", dir_path)
        dir_path.mkdir(parents=True, exist_ok=True)
        return True
    except PermissionError:
        logger.error(
            (
                "PermissionError: failed to create %s\n"
                + "Try using sudo to fix the issue 😃"
            ),
            dir_path,
        )
    except NotADirectoryError:
        logger.error(
            "usage error: non-directory target. Ansible Generate should be "
            + "directed to a directory"
        )
    except Exception:
        logger.error("failed to create %s", dir_path, exc_info=True)
    return False


def get_alternate_inventories_directory_paths(
    logger: Logger, inventories: Iterable[str]
) -> Set[str]:
    """

    Args:
         inventories: An array of string inventory names

    Returns:
        An array of directories that are required based on the provided
        inventory names.
    """
    logger.debug('msg="building alternate inventory layout directory paths"')
    inventory_paths = {"roles"}
    for inventory in inventories:
        inventory_paths.update(
            {
                f"inventories/{inventory}/group_vars",
                f"inventories/{inventory}/host_vars",
            }
        )
    return inventory_paths
