"""
Generate gitignored .chalice/config.json for deploy and then run deploy.
Takes on parameter for now: stage (either "dev" or "prod")
"""
from dcicutils.misc_utils import PRINT
import os
import sys
import argparse
import json
import subprocess

from dcicutils.misc_utils import as_seconds, ignored


# TODO: Move to dcicutils.command_utils.
def subprocess_call(command: list, verbose: bool = False, **kwargs) -> int:
    """
    Executes the given command (list of arguments, the main command as the zeroth)
    in a sub-process and returns the return code from the command execution.
    Prints (to stdout) before/after execution messages if ``verbose`` is True.
    If general (not command execution related) exception occurs raises that exception.

    :param command: Command as list with zeroth item being the main command.
    :param verbose: If True prints (to stdout) before/after execution messages.
    :return: Return code from the command execution.
    """
    if verbose:
        command_string = " ".join(command)
    try:
        if verbose:
            PRINT(f"Executing command in sub-process: {command_string}")
        return_code = subprocess.check_call(command, **kwargs)
        if verbose:
            PRINT(f"Done executing (return-code: {return_code}) command in sub-process: {command_string}")
    except subprocess.CalledProcessError as command_exception:
        return_code = command_exception.returncode
        if verbose:
            PRINT(f"Error executing (return-code: {return_code}) command in sub-process: {command_string}")
    except Exception as general_exception:
        if verbose:
            PRINT(f"Exception executing command ({command_string}) in sub-process: {general_exception}")
        raise general_exception
    return return_code


class Deploy(object):

    DEFAULT_LAMBDA_TIMEOUT = as_seconds(minutes=15)

    CONFIG_BASE = {
      "stages": {
        "dev": {
          "api_gateway_stage": "api",
          "autogen_policy": False,
          "lambda_memory_size": 512,
          "lambda_timeout": DEFAULT_LAMBDA_TIMEOUT,  # 15 mins in seconds
          "environment_variables": {
              "chalice_stage": "dev"
          }
        },
        "prod": {
          "api_gateway_stage": "api",
          "autogen_policy": False,
          "lambda_memory_size": 512,
          "lambda_timeout": DEFAULT_LAMBDA_TIMEOUT,  # 15 mins in seconds
          "environment_variables": {
              "chalice_stage": "prod"
          }
        }
      },
      "version": "2.0",
      "app_name": "foursight-cgap",
      "layers": [
          "arn:aws:lambda:us-east-1:553035198032:layer:git:11"  # required for Deployment Checks - Will 5/20/2020
      ]
    }

    config_dir = os.path.dirname(__file__)

    @classmethod
    def get_config_filepath(cls):
        return os.path.join(cls.config_dir, '.chalice/config.json')

    @classmethod
    # dmichaels/2022-07-22/C4-826:
    # Added identity and stack_name args for environment variables in the Foursight CloudFormation template.
    # The identity is the global application configuration (GAC) name from which we read other pertinent
    # secrets/values (e.g. CLIENT_ID/SECRET) at runtime. The stack_name is the Foursight CloudFormation
    # stack name which provides a foothold to get other stack related values at runtime (e.g. the name
    # of the CheckerRunner lambda function generated by chalice at foursight provision time, which we
    # get by looking for the AWS lambda function using the regex "{stack_name}-CheckRunner-.*").
    def build_config(cls, stage, identity=None, stack_name=None,
                     trial_creds=None, trial_global_env_bucket=False, global_env_bucket=None,
                     security_group_ids=None, subnet_ids=None, check_runner=None,
                     lambda_timeout=DEFAULT_LAMBDA_TIMEOUT, is_foursight_fourfront=False,
                     is_foursight_smaht=False):
        """ Builds the chalice config json file. See: https://aws.github.io/chalice/topics/configfile"""
        # dmichaels/2022-07-22/C4-826:
        # Removed value from the Foursight CloudFormation template; get from GAC/etc at runtime.
        ignored(stage)
        if trial_creds:
            # key to decrypt access key
            # s3_enc_secret = trial_creds['S3_ENCRYPT_KEY']
            # client_id = trial_creds['CLIENT_ID']
            # client_secret = trial_creds['CLIENT_SECRET']
            dev_secret = None
            # es_host = trial_creds['ES_HOST']
            # env_name = trial_creds['ENV_NAME']
            # rds_name = trial_creds['RDS_NAME']
            # s3_key_id = trial_creds.get('S3_ENCRYPT_KEY_ID')
            # if not (s3_enc_secret and client_id and client_secret and es_host and rds_name):
            #     PRINT(''.join(['ERROR. You are missing one more more environment',
            #                    'variables needed to deploy the Foursight trial. Need:\n',
            #                    'S3_ENCRYPT_KEY, CLIENT_ID, CLIENT_SECRET, ES_HOST, RDS_NAME in trial_creds dict.'])
            #           )
            #     sys.exit()
        else:
            # s3_enc_secret = os.environ.get("S3_ENCRYPT_KEY")
            # client_id = os.environ.get("CLIENT_ID")
            # client_secret = os.environ.get("CLIENT_SECRET")
            dev_secret = os.environ.get("DEV_SECRET")
            # es_host = None  # not previously passed to config
            # env_name = None
            # s3_key_id = None  # not supported in legacy
            # if not (s3_enc_secret and client_id and client_secret and dev_secret):
            #     PRINT(''.join(['ERROR. You are missing one more more environment ',
            #                    'variables needed to deploy Foursight.\n',
            #                    'Need: S3_ENCRYPT_KEY, CLIENT_ID, CLIENT_SECRET, DEV_SECRET.'])
            #           )
            #     sys.exit()
        for curr_stage_name in ['dev', 'prod']:
            curr_stage = cls.CONFIG_BASE['stages'][curr_stage_name]
            curr_stage_environ = curr_stage['environment_variables']

            # dmichaels/2022-07-22/C4-826:
            # Added these to the Foursight CloudFormation template; get other values from GAC/etc at runtime.
            curr_stage_environ['IDENTITY'] = identity
            curr_stage_environ['STACK_NAME'] = stack_name
            # dmichaels/2022-07-22/C4-826:
            # Removed these from the Foursight CloudFormation template; get from GAC/etc at runtime.
            # curr_stage_environ['S3_ENCRYPT_KEY'] = s3_enc_secret
            # curr_stage_environ['CLIENT_ID'] = client_id
            # curr_stage_environ['CLIENT_SECRET'] = client_secret
            # if rds_name:
            #     curr_stage_environ['RDS_NAME'] = rds_name
            if dev_secret:  # still pass in main account, ignored in alpha infra - Will Aug 24 2021
                curr_stage_environ['DEV_SECRET'] = dev_secret
            # dmichaels/2022-07-22/C4-826:
            # Removed these from the Foursight CloudFormation template; get from GAC/etc at runtime.
            # if env_name:
            #     curr_stage_environ['ENV_NAME'] = env_name
            # if es_host:
            #     curr_stage_environ['ES_HOST'] = es_host
            # if s3_key_id:
            #     curr_stage_environ['S3_ENCRYPT_KEY_ID'] = s3_key_id
            if trial_global_env_bucket:
                # in the trial account setup, use a shorter timeout
                curr_stage['lambda_timeout'] = lambda_timeout
                if not global_env_bucket:
                    global_bucket_env_from_environ = os.environ.get('GLOBAL_BUCKET_ENV')
                    global_env_bucket_from_environ = os.environ.get('GLOBAL_ENV_BUCKET')
                    if (global_bucket_env_from_environ
                            and global_env_bucket_from_environ
                            and global_bucket_env_from_environ != global_env_bucket_from_environ):
                        PRINT('ERROR. GLOBAL_BUCKET_ENV and GLOBAL_ENV_BUCKET are both set, but inconsistently.')
                        sys.exit(1)
                    global_env_bucket = global_bucket_env_from_environ or global_env_bucket_from_environ
                if global_env_bucket:
                    curr_stage_environ['GLOBAL_BUCKET_ENV'] = global_env_bucket  # legacy compatibility
                    curr_stage_environ['GLOBAL_ENV_BUCKET'] = global_env_bucket
                else:
                    PRINT('ERROR. GLOBAL_ENV_BUCKET must be set or global_env_bucket= must be passed'
                          ' when building the trial config.')
                    sys.exit(1)
            if security_group_ids:
                curr_stage['security_group_ids'] = security_group_ids
            if subnet_ids:
                curr_stage['subnet_ids'] = subnet_ids
            if check_runner:
                curr_stage_environ['CHECK_RUNNER'] = check_runner

        filename = cls.get_config_filepath()
        PRINT(''.join(['Writing: ', filename]))
        with open(filename, 'w') as config_file:
            config_file.write(json.dumps(cls.CONFIG_BASE))
        PRINT(''.join(['Done writing: ', filename]))
        # Export poetry into requirements.
        # Note that depending on is_foursight_fourfront we include either foursight_fourfront or foursight_cgap;
        # these names are poetry group names defined in 4dn-cloud-infra/pyproject.toml. dmichaels/2022-11-01.
        if is_foursight_fourfront:
            subprocess_call(
                ['poetry', 'export', '-f', 'requirements.txt', '--without-hashes', '--with', 'foursight_fourfront', '-o', 'requirements.txt'], verbose=True)
        elif is_foursight_smaht:
            subprocess_call(
                ['poetry', 'export', '-f', 'requirements.txt', '--without-hashes', '--with', 'foursight_smaht',
                 '-o', 'requirements.txt'], verbose=True)
        else:
            subprocess_call(
                ['poetry', 'export', '-f', 'requirements.txt', '--without-hashes', '--with', 'foursight_cgap', '-o', 'requirements.txt'], verbose=True)

    @classmethod
    def build_config_and_deploy(cls, stage):
        cls.build_config(stage)
        # actually deploy
        subprocess_call(['chalice', 'deploy', '--stage', stage], verbose=True)

    @classmethod
    # dmichaels/2022-07-22/C4-826:
    # Added identity arg for the Foursight CloudFormation template; get other values from GAC/etc at runtime.
    def build_config_and_package(cls, args, identity=None, stack_name=None, trial_creds=None, global_env_bucket=None,
                                 security_ids=None, subnet_ids=None, check_runner=None,
                                 lambda_timeout=DEFAULT_LAMBDA_TIMEOUT,
                                 # These next args are preferred over passing 'args'.
                                 merge_template=None, output_file=None, stage=None, trial=None,
                                 ):
        """ Builds a config with a special case for the trial account. For the trial account, expects a dictionary of
            environment variables, a list of security group ids, and a list of subnet ids. Finally, packages as a
            Cloudformation template."""

        # Determine if we're packaging Foursight-CGAP or Foursight-Fourfront, based on
        # the provision stack name setup in 4dn-cloud-infra/stack.py. This is used to
        # conditionally include the appropriate library (foursight-cgap or foursight)
        # in the Chalice package. dmichaels/2022-11-01.
        # Added smaht to this to minimally perturb existing structure, but should be
        # refactored at a later time to clean up and use EnvUtils
        _4DN_CLOUD_INFRA_FOURSIGHT_FOURFRONT_PROVISION_TARGETS = ["foursight-development", "foursight-production"]
        _4DN_CLOUD_INFRA_FOURSIGHT_SMAHT_PROVISION_TARGETS = ["foursight-smaht"]
        is_foursight_fourfront = False
        is_foursight_smaht = False
        if args.stack in _4DN_CLOUD_INFRA_FOURSIGHT_FOURFRONT_PROVISION_TARGETS:
            is_foursight_fourfront = True
        elif args.stack in _4DN_CLOUD_INFRA_FOURSIGHT_SMAHT_PROVISION_TARGETS:
            is_foursight_smaht = True

        # For compatibility during transition, we allow these argument to be passed in lieu of args.
        if merge_template is None:
            merge_template = args.merge_template
        if output_file is None:
            output_file = args.output_file
        if stage is None:
            stage = args.stage
        if trial is None:
            trial = args.trial

        if trial:
            # Note we no longer need check_runner (previously checked here) as it is determined
            # dynamically at runtime (in identity.set_check_runner_lambda_environment_variable);
            # should have been removed before. dmichaels/2022-11-01.
            if trial_creds and security_ids and subnet_ids:
                # dmichaels/2022-07-22/C4-826:
                # Added identity arg for the Foursight CloudFormation template.
                cls.build_config(stage, identity=identity, stack_name=stack_name, trial_creds=trial_creds,
                                 trial_global_env_bucket=True,
                                 global_env_bucket=global_env_bucket, lambda_timeout=lambda_timeout,
                                 security_group_ids=security_ids, subnet_ids=subnet_ids, check_runner=check_runner,
                                 is_foursight_fourfront=is_foursight_fourfront, is_foursight_smaht=is_foursight_smaht)
            else:
                raise Exception('Build config requires trial_creds, sg id, and subnet ids to run in trial account')
        else:
            cls.build_config(stage=stage)
        # actually package cloudformation templates
        # add --single-file ?
        flags = ['--stage', stage, '--pkg-format', 'cloudformation', '--template-format', 'yaml']
        if merge_template:
            flags.extend(['--merge-template', merge_template])
        PRINT(f"Starting chalice package: {output_file}")
        subprocess_call(['chalice', 'package', *flags, output_file], verbose=True)
        PRINT(f"Finished chalice package: {output_file}")

        # dmichaels/2022-12-16/C4-???:
        # We reached the maximum size of a Chalice package to deploy in AWS (220,476,770 bytes)
        # Prune out of it unnecessary modules, which look like tests and/or examples.
        # This prune process is done by this bash script which lives in 4dn-cloud-infra:
        # scripts/prune_chalice_package.sh.
        prune_chalice_package_script_file = os.path.join(os.getcwd(), "scripts/prune_chalice_package.sh")
        if os.path.exists(prune_chalice_package_script_file):
            chalice_package_file = os.path.join(output_file, "deployment.zip")
            if not os.path.exists(chalice_package_file):
                PRINT(f"WARNING: Chalice package file not found: {chalice_package_file}")
                PRINT(f"WARNING: Not running chalice prune script.")
            else:
                PRINT(f"Found chalice prune script: {prune_chalice_package_script_file}")
                PRINT(f"Starting chalice package prune: {chalice_package_file}")
                subprocess_call([prune_chalice_package_script_file, chalice_package_file], verbose=True)
                PRINT(f"Finished chalice package prune: {chalice_package_file}")
        else:
            PRINT(f"No chalice prune script found.")


def main():
    parser = argparse.ArgumentParser('chalice_deploy')
    parser.add_argument(
        "stage",
        type=str,
        choices=['dev', 'prod'],
        help="chalice deployment stage. Must be one of 'prod' or 'dev'")
    args = parser.parse_args()
    Deploy.build_config_and_deploy(stage=args.stage)


if __name__ == '__main__':
    main()
