import os
import shlex
import subprocess
from pathlib import Path


def wget(url: str, output_path: str | Path) -> None:
    """Retrieves content at the url and stores it at an output path.

    Parameters
    ----------
    url
        The url to retrieve the content from.
    output_path
        Where we'll save the output to.

    """
    subprocess.run(shlex.split(f"wget -O {output_path} {url}"), check=True)


def unzip_and_delete_archive(archive_path: str | Path, output_path: str | Path) -> None:
    """Unzips an archive file to a directory and then deletes the archive.

    Parameters
    ----------
    archive_path
        The path to the archive we want to unzip.
    output_path
        The place to store the unzipped contents.

    """
    subprocess.run(shlex.split(f"unzip {archive_path} -d {output_path}"), check=True)
    subprocess.run(shlex.split(f"rm {archive_path}"), check=True)


def mkdir(
    path: str | Path,
    mode: int = 0o775,
    *,
    exist_ok: bool = False,
    parents: bool = False,
) -> None:
    """Creates a directory and its parents with the specified mode.

    This method is meant to combat permissions errors generated by the default
    umask behavior when creating parent directories (i.e. ignore the mode
    argument and use the default permissions).

    Parameters
    ----------
    path
        The path of the directory to create.
    mode
        The permission mode to use in directory creation.
    exist_ok
        If False, raises FileExistsError if the directory already exists.
    parents
        If False, raises FileNotFoundError if the directory's parent doesn't
        exist.

    """
    path = Path(path)
    old_umask = os.umask(0o777 - mode)
    try:
        path.mkdir(exist_ok=exist_ok, parents=parents)
    finally:
        os.umask(old_umask)


def touch(
    path: str | Path,
    mode: int = 0o664,
    *,
    exist_ok: bool = False,
    clobber: bool = False,
) -> None:
    """Creates a file with the specified mode.

    Parameters
    ----------
    path
        The path of the file to create.
    mode
        The permission mode to use in file creation.
    exist_ok
        If False, raises FileExistsError if the file already exists.
        If True, raises FileExistsError if path is a directory or permissions
        do not match the mode argument.
    clobber
        If True, overwrites the file if it already exists.

    """
    path = Path(path)
    if path.exists():
        if not path.is_file():
            msg = f"File exists at {path} and is not a file."
            raise FileExistsError(msg)
        if not exist_ok and not clobber:
            msg = f"File exists at {path}."
            raise FileExistsError(msg)

        if clobber:
            path.unlink()
            _touch_clean(path, mode)
        else:
            path_chmod = path.stat().st_mode & 0o777
            if path_chmod != mode:
                msg = (
                    f"File exists at {path} with mode {oct(path_chmod)} "
                    f"and not {oct(mode)}."
                )
                raise FileExistsError(msg)
    else:
        _touch_clean(path, mode)


def _touch_clean(path: str | Path, mode: int = 0o664) -> None:
    """Creates a file with the specified mode, overwriting the file if it exists.

    This function is a helper function for the `touch` function. It is not
    meant to be used outside of this module.

    Parameters
    ----------
    path
        The path of the file to create.
    mode
        The permission mode to use in file creation.

    """
    path = Path(path)
    old_umask = os.umask(0o777 - mode)
    try:
        path.touch()
    finally:
        os.umask(old_umask)
