import hashlib
import json
import logging
import os
import subprocess
import sys
import time
from collections import defaultdict

from shutil import copy
from shutil import copyfile
from shutil import copystat
from shutil import copytree
from tempfile import mkdtemp

import boto3
import botocore
import yaml
import sys

from .helpers import archive
from .helpers import get_environment_variable_value
from .helpers import LambdaContext
from .helpers import mkdir
from .helpers import read
from .helpers import timestamp


ARN_PREFIXES = {
    "cn-north-1": "aws-cn",
    "cn-northwest-1": "aws-cn",
    "us-gov-west-1": "aws-us-gov",
}

log = logging.getLogger(__name__)


def load_source(module_name, module_path):
    """Loads a python module from the path of the corresponding file."""

    if sys.version_info[0] == 3 and sys.version_info[1] >= 5:
        import importlib.util
        spec = importlib.util.spec_from_file_location(module_name, module_path)
        module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(module)
    elif sys.version_info[0] == 3 and sys.version_info[1] < 5:
        import importlib.machinery
        loader = importlib.machinery.SourceFileLoader(module_name, module_path)
        module = loader.load_module()
    return module


def cleanup_old_versions(
    src, keep_last_versions, config_file="config.yaml", profile_name=None,
):
    """Deletes old deployed versions of the function in AWS Lambda.

    Won't delete $Latest and any aliased version

    :param str src:
        The path to your Lambda ready project (folder must contain a valid
        config.yaml and handler module (e.g.: service.py).
    :param int keep_last_versions:
        The number of recent versions to keep and not delete
    """
    if keep_last_versions <= 0:
        print("Won't delete all versions. Please do this manually")
    else:
        path_to_config_file = os.path.join(src, config_file)
        cfg = read_cfg(path_to_config_file, profile_name)

        profile_name = cfg.get("profile")
        aws_access_key_id = cfg.get("aws_access_key_id")
        aws_secret_access_key = cfg.get("aws_secret_access_key")

        client = get_client(
            "lambda",
            profile_name,
            aws_access_key_id,
            aws_secret_access_key,
            cfg.get("region"),
        )

        response = client.list_versions_by_function(
            FunctionName=cfg.get("function_name"),
        )
        versions = response.get("Versions")
        if len(response.get("Versions")) < keep_last_versions:
            print("Nothing to delete. (Too few versions published)")
        else:
            version_numbers = [
                elem.get("Version") for elem in versions[1:-keep_last_versions]
            ]
            for version_number in version_numbers:
                try:
                    client.delete_function(
                        FunctionName=cfg.get("function_name"),
                        Qualifier=version_number,
                    )
                except botocore.exceptions.ClientError as e:
                    print(f"Skipping Version {version_number}: {e}")


def deploy(
    src,
    requirements=None,
    local_package=None,
    config_file="config.yaml",
    profile_name=None,
    preserve_vpc=False,
):
    """Deploys a new function to AWS Lambda.

    :param str src:
        The path to your Lambda ready project (folder must contain a valid
        config.yaml and handler module (e.g.: service.py).
    :param str local_package:
        The path to a local package with should be included in the deploy as
        well (and/or is not available on PyPi)
    """
    # Load and parse the config file.
    path_to_config_file = os.path.join(src, config_file)
    cfg = read_cfg(path_to_config_file, profile_name)

    # Copy all the pip dependencies required to run your code into a temporary
    # folder then add the handler file in the root of this directory.
    # Zip the contents of this folder into a single file and output to the dist
    # directory.
    path_to_zip_file = build(
        src,
        config_file=config_file,
        requirements=requirements,
        local_package=local_package,
    )

    existing_config = get_function_config(cfg)
    if existing_config:
        update_function(
            cfg, path_to_zip_file, existing_config, preserve_vpc=preserve_vpc
        )
    else:
        create_function(cfg, path_to_zip_file)


def deploy_s3(
    src,
    requirements=None,
    local_package=None,
    config_file="config.yaml",
    profile_name=None,
    preserve_vpc=False,
):
    """Deploys a new function via AWS S3.

    :param str src:
        The path to your Lambda ready project (folder must contain a valid
        config.yaml and handler module (e.g.: service.py).
    :param str local_package:
        The path to a local package with should be included in the deploy as
        well (and/or is not available on PyPi)
    """
    # Load and parse the config file.
    path_to_config_file = os.path.join(src, config_file)
    cfg = read_cfg(path_to_config_file, profile_name)

    # Copy all the pip dependencies required to run your code into a temporary
    # folder then add the handler file in the root of this directory.
    # Zip the contents of this folder into a single file and output to the dist
    # directory.
    path_to_zip_file = build(
        src,
        config_file=config_file,
        requirements=requirements,
        local_package=local_package,
    )

    use_s3 = True