"""
Initialize an arbitrary project
"""

import functools
import logging
import shutil

from cookiecutter import config, exceptions, repository

from samcli.lib.utils import osutils

from .exceptions import ArbitraryProjectDownloadFailed

LOG = logging.getLogger(__name__)


BAD_LOCATION_ERROR_MSG = (
    "Please verify your location. The following types of location are supported:"
    "\n\n* Github: gh:user/repo (or) https://github.com/user/repo (or) git@github.com:user/repo.git"
    "\n          For Git repositories, you must use location of the root of the repository."
    "\n\n* Mercurial: hg+ssh://hg@bitbucket.org/repo"
    "\n\n* Http(s): https://example.com/code.zip"
    "\n\n* Local Path: /path/to/code.zip"
)


def generate_non_cookiecutter_project(location, output_dir):
    """
    Uses Cookiecutter APIs to download a project at given ``location`` to the ``output_dir``.
    This does *not* run cookiecutter on the downloaded project.

    Parameters
    ----------
    location : str
        Path to where the project is. This supports all formats of location cookiecutter supports
        (ex: zip, git, ssh, hg, local zipfile)

        NOTE: This value *cannot* be a local directory. We didn't see a value in simply copying the directory
        contents to ``output_dir`` without any processing.

    output_dir : str
        Directory where the project should be downloaded to

    Returns
    -------
    str
        Name of the directory where the project was downloaded to.

    Raises
    ------
    cookiecutter.exception.CookiecutterException if download failed for some reason
    """

    LOG.debug("Downloading project from %s to %s", location, output_dir)

    # Don't prompt ever
    no_input = True

    # Expand abbreviations in URL such as gh:awslabs/aws-sam-cli
    location = repository.expand_abbreviations(location, config.BUILTIN_ABBREVIATIONS)

    # If this is a zip file, download and unzip into output directory
    if repository.is_zip_file(location):
        LOG.debug("%s location is a zip file", location)
        download_fn = functools.partial(
            repository.unzip, zip_uri=location, is_url=repository.is_repo_url(location), no_input=no_input
        )

    # Else, treat it as a git/hg/ssh URL and try to clone
    elif repository.is_repo_url(location):
        LOG.debug("%s location is a source control repository", location)
        download_fn = functools.partial(repository.clone, repo_url=location, no_input=no_input)

    else:
        raise ArbitraryProjectDownloadFailed(msg=BAD_LOCATION_ERROR_MSG)

    try:
        return _download_and_copy(download_fn, output_dir)
    except exceptions.RepositoryNotFound as ex:
        # Download failed because the zip or the repository was not found
        raise ArbitraryProjectDownloadFailed(msg=BAD_LOCATION_ERROR_MSG) from ex


def _download_and_copy(download_fn, output_dir):
    """
    Runs the download function to download files into a temporary directory and then copy the files over to
    the ``output_dir``

    Parameters
    ----------
    download_fn : function
        Method to be called to download. It needs to accept a parameter called `clone_to_dir`. This will be
        set to the temporary directory

    output_dir : str
        Path to the directory where files will be copied to

    Returns
    -------
    output_dir
    """

    with osutils.mkdir_temp(ignore_errors=True) as tempdir:
        downloaded_dir = download_fn(clone_to_dir=tempdir)
        osutils.copytree(downloaded_dir, output_dir, ignore=shutil.ignore_patterns("*.git"))

    return output_dir
