import logging
import os
import subprocess
import sys
from contextlib import suppress
from pathlib import Path
from shutil import copytree, ignore_patterns, make_archive, rmtree

import boto3
import click

from yappa.handlers.common import DEFAULT_CONFIG_FILENAME
from yappa.packaging.common import validate_requirements_file
from yappa.settings import (
    DEFAULT_IGNORED_FILES,
    DEFAULT_PACKAGE_DIR,
    DEFAULT_REQUIREMENTS_FILE,
    HANDLERS_DIR,
    YANDEX_S3_URL,
)
from yappa.utils import get_yc_entrypoint

"""
limitations:
- 128 MB - resulting zip archive
"""

logger = logging.getLogger(__name__)


def prepare_package(
    requirements_file=DEFAULT_REQUIREMENTS_FILE,
    ignored_files=DEFAULT_IGNORED_FILES,
    tmp_dir=DEFAULT_PACKAGE_DIR,
    to_install_requirements=True,
    config_filename=DEFAULT_CONFIG_FILENAME,
):
    """
    prepares package folder
    - copy project files
    - copy handler.py
    - install packages
    """
    validate_requirements_file(requirements_file)

    logger.info("Copying project files to %s", tmp_dir)
    with suppress(FileExistsError):
        os.mkdir(tmp_dir)
    copytree(
        os.getcwd(),
        tmp_dir,
        ignore=ignore_patterns(*ignored_files, tmp_dir, requirements_file),
        dirs_exist_ok=True,
    )
    copytree(
        Path(Path(__file__).resolve().parent.parent, HANDLERS_DIR),
        Path(tmp_dir, "handlers"),
        dirs_exist_ok=True,
    )
    os.rename(
        Path(tmp_dir, config_filename), Path(tmp_dir, DEFAULT_CONFIG_FILENAME)
    )
    if to_install_requirements:
        logger.info("Installing requirements...")
        cmd = (
            f"{sys.executable} -m pip install "
            f"-r {requirements_file} -t {tmp_dir} --upgrade --quiet"
        )
        subprocess.check_call(cmd.split())
    return tmp_dir


def ensure_bucket(bucket_name, aws_access_key_id, aws_secret_access_key):
    s3 = boto3.resource(
        "s3",
        aws_access_key_id=aws_access_key_id,
        aws_secret_access_key=aws_secret_access_key,
        endpoint_url=YANDEX_S3_URL,
    )
    bucket = s3.Bucket(bucket_name)
    try:
        bucket.create()
    except Exception as e:
        if e.__class__.__name__ != "BucketAlreadyOwnedByYou":
            raise
    return bucket


def upload_to_bucket(
    folder, bucket_name, aws_access_key_id, aws_secret_access_key
):
    """
    makes archive, uploads to bucket, deletes tmp archive
    """
    logger.info("Creating zip package")
    archive_path = make_archive(folder, "zip", folder)
    archive_filename = os.path.basename(archive_path)
    try:
        bucket = ensure_bucket(
            bucket_name,
            aws_access_key_id=aws_access_key_id,
            aws_secret_access_key=aws_secret_access_key,
        )
        bucket.upload_file(archive_filename, archive_filename)
    finally:
        os.remove(archive_filename)
        rmtree(folder)
    return archive_filename


def delete_bucket(bucket_name, aws_access_key_id, aws_secret_access_key):
    """
    deletes bucket from s3
    """
    bucket = ensure_bucket(
        bucket_name,
        aws_access_key_id=aws_access_key_id,
        aws_secret_access_key=aws_secret_access_key,
    )
    bucket.objects.all().delete()
    bucket.delete()


def create_function_version(yc, config, config_filename):
    click.echo("Preparing package...")
    package_dir = prepare_package(
        config["requirements_file"],
        config["excluded_paths"],
        to_install_requirements=True,
        config_filename=config_filename,
    )
    click.echo(f"Uploading to bucket {config['bucket']}...")
    object_key = upload_to_bucket(
        package_dir,
        config["bucket"],
        **yc.get_s3_key(config["service_account_names"]["creator"]),
    )
    click.echo(
        "Creating new function version for "
        + click.style(config["project_slug"], bold=True)
    )
    yc.create_function_version(
        config["project_slug"],
        runtime=config["runtime"],
        description=config["description"],
        bucket_name=config["bucket"],
        object_name=object_key,
        entrypoint=get_yc_entrypoint(
            config["application_type"], config["entrypoint"]
        ),
        memory=config["memory_limit"],
        service_account_id=config["service_account_id"],
        timeout=config["timeout"],
        named_service_accounts=config["named_service_accounts"],
        environment=config["environment"],
    )
    click.echo("Created function version")
    access_changed = yc.set_function_access(
        function_name=config["project_slug"], is_public=config["is_public"]
    )
    if access_changed:
        click.echo(
            f"Changed function access. Now it is "
            f" {'not' if config['is_public'] else 'open to'} public"
        )

    if config["django_settings_module"]:
        yc.create_function_version(
            config["manage_function_name"],
            runtime=config["runtime"],
            description=config["description"],
            bucket_name=config["bucket"],
            object_name=object_key,
            entrypoint=get_yc_entrypoint("manage", config["entrypoint"]),
            memory=config["memory_limit"],
            service_account_id=config["service_account_id"],
            timeout=300,
            named_service_accounts=config["named_service_accounts"],
            environment=config["environment"],
        )
