import types
import logging
import hashlib
import botocore
import yaml

logger = logging.getLogger(__file__)


def get_hash_for_template(template):
    hasher = hashlib.md5()
    hasher.update(str.encode(template))
    return "{}{}".format('a', hasher.hexdigest())


def create_or_update(self, **kwargs):
    stack_name = kwargs.get('StackName')

    is_first_run = True
    try:
        self.describe_stacks(
            StackName=stack_name
        )
        is_first_run = False
    except self.exceptions.ClientError as e:
        if "Stack with id {} does not exist".format(stack_name) not in str(e):
            raise e

    if is_first_run:
        self.create_stack(**kwargs)
        waiter = self.get_waiter('stack_create_complete')
        waiter.wait(StackName=stack_name)
    else:
        change_set_name = get_hash_for_template(kwargs.get('TemplateBody'))
        self.create_change_set(
            ChangeSetName=change_set_name,
            ChangeSetType="UPDATE",
            **kwargs,
        )
        change_set_create_complete_waiter = self.get_waiter('change_set_create_complete')
        try:
            change_set_create_complete_waiter.wait(
                ChangeSetName=change_set_name,
                StackName=stack_name,
            )
        except botocore.exceptions.WaiterError as e:
            if "No changes to build for stack" not in str(e):
                raise e

        response = self.describe_change_set(
            ChangeSetName=change_set_name,
            StackName=stack_name
        )
        change_set = response.get('Changes')
        logger.info('Changes:' + yaml.safe_dump(change_set))
        if len(change_set) > 0:
            logger.info('Executing change set: {}'.format(stack_name))
            self.execute_change_set(
                ChangeSetName=change_set_name,
                StackName=stack_name,
            )
            logger.info('Waiting for change set to execute: {}'.format(stack_name))
            waiter = self.get_waiter('stack_update_complete')
            waiter.wait(
                StackName=stack_name
            )
            logger.info('Finished stack: {}'.format(stack_name))
        else:
            logger.info('No changes to build for stack: {}'.format(stack_name))
            logger.info('Finished stack: {}'.format(stack_name))


def make_better(client):
    client.create_or_update = types.MethodType(create_or_update, client)
    return client
