################################################################################
#  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.          #
#                                                                              #
#  Licensed under the Apache License, Version 2.0 (the "License").             #
#  You may not use this file except in compliance with the License.            #
#  A copy of the License is located at                                         #
#                                                                              #
#      http://www.apache.org/licenses/LICENSE-2.0                              #
#                                                                              #
#  or in the 'license' file accompanying this file. This file is distributed   #
#  on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express  #
#  or implied. See the License for the specific language governing             #
#  permissions and limitations under the License.                              #
################################################################################
""" Lex V2 CloudFormation Custom Resource Lambda Handler"""

from os import getenv

from crhelper import CfnResource

# our own modules
# pylint: disable=import-error
from shared.client import get_client  # type: ignore
from shared.logger import get_logger, get_log_level  # type: ignore
from lex_v2_cfn_cr import LexV2CustomResource  # type: ignore # pylint: disable=no-name-in-module

# pylint: enable=import-error

LOGGER = get_logger(__name__)
HELPER = CfnResource(json_logging=True, log_level=get_log_level())

# global init code goes here so that it can pass failure in case
# of an exception
try:
    # boto3 client
    CLIENT = get_client("lexv2-models")

    # how long to wait between resource polling
    POLL_SLEEP_TIME_IN_SECS = int(getenv("POLL_SLEEP_TIME_IN_SECS", "5"))

    LEX_CUSTOM_RESOURCE = LexV2CustomResource(
        client=CLIENT,
        logger=LOGGER,
        poll_sleep_time_in_secs=POLL_SLEEP_TIME_IN_SECS,
    )
except Exception as exception:  # pylint: disable=broad-except
    HELPER.init_failure(exception)


def wait_for_bot_locales_build(bot_id, bot_locale_ids):
    """Waits for bot locales build"""
    # NOTE: not using the cr helper poller functionality since there's a 8K limit
    # in the CloudWatch Event input that it uses and medium size bots may trigger
    # it during updates as the payload includes JSON encoded current and old
    # resource properties
    try:
        LEX_CUSTOM_RESOURCE.build_bot_locales(
            bot_id=bot_id,
            bot_locale_ids=bot_locale_ids,
        )
    except Exception as exception:  # pylint: disable=broad-except
        HELPER.Status = "FAILED"
        HELPER.Reason = str(exception)
        HELPER.PhysicalResourceId = bot_id


@HELPER.create
def create_resource(event, _):
    """Create Resource"""
    resource_type = event["ResourceType"]
    resource_properties = event["ResourceProperties"]

    if resource_type == "Custom::LexBot":
        response = LEX_CUSTOM_RESOURCE.create_bot(resource_properties=resource_properties)
        HELPER.Data = response

        bot_id = response.get("botId")
        bot_locale_ids = response.get("botLocaleIds")
        _exception = response.get("_exception")
        if bot_id and _exception:
            # This allows to delete the bot if an exception was raised after
            # the bot was created. E.g. while creating the locale, intents, slot, etc.
            HELPER.Status = "FAILED"
            HELPER.Reason = _exception
            HELPER.PhysicalResourceId = bot_id
        else:
            wait_for_bot_locales_build(bot_id=bot_id, bot_locale_ids=bot_locale_ids)

        return bot_id

    if resource_type == "Custom::LexBotVersion":
        response = LEX_CUSTOM_RESOURCE.create_bot_version(resource_properties=resource_properties)
        HELPER.Data = response
        bot_version = response["botVersion"]

        return bot_version

    if resource_type == "Custom::LexBotAlias":
        response = LEX_CUSTOM_RESOURCE.create_bot_alias(resource_properties=resource_properties)
        HELPER.Data = response
        bot_alias_id = response["botAliasId"]

        return bot_alias_id

    raise RuntimeError(f"Invalid resource type: {resource_type}")


@HELPER.poll_delete
def poll_delete(event, _):
    """Poll Delete"""
    resource_type = event["ResourceType"]
    helper_data = event["CrHelperData"]

    if resource_type == "Custom::LexBot":
        bot_id = helper_data.get("botId")
        if bot_id:
            LEX_CUSTOM_RESOURCE.wait_for_delete_bot(bot_id=bot_id)

        return True

    if resource_type == "Custom::LexBotVersion":
        return True

    if resource_type == "Custom::LexBotAlias":
        bot_id = helper_data.get("botId")
        bot_alias_id = helper_data.get("botAliasId")
        if bot_id and bot_alias_id:
            LEX_CUSTOM_RESOURCE.wait_for_delete_bot_alias(bot_id=bot_id, bot_alias_id=bot_alias_id)
        return True

    raise RuntimeError(f"Invalid resource type: {resource_type}")


@HELPER.delete
def delete_resource(event, _):
    """Delete Resource"""
    resource_type = event["ResourceType"]
    resource_properties = event["ResourceProperties"]

    if resource_type == "Custom::LexBot":
        physical_resource_id = event.get("PhysicalResourceId", "")
        # Handle resource cancellation deletes during creation and cases where CloudFormation
        # passes a system generated PhysicalResourceId which is not a botId.
        # Valid Bot IDs are a fixed lenght of 10 alphanumeric characters:
        # https://docs.aws.amazon.com/lexv2/latest/dg/API_CreateBot.html#lexv2-CreateBot-response-botId
        if (
            not physical_resource_id
            or not physical_resource_id.isalnum()
            or len(physical_resource_id) != 10
        ):
            bot_name = resource_properties.get("botName", "")
            LOGGER.warning(
                "unable to find a valid Physical Resource ID - "
                "trying to obtain it from the resource properties botName: %s",
                bot_name,
            )
            bot_id = LEX_CUSTOM_RESOURCE.get_bot_id(bot_name=bot_name)
        else:
            bot_id = physical_resource_id

        HELPER.Data["botId"] = bot_id

        if bot_id:
            try:
                LEX_CUSTOM_RESOURCE.delete_bot(bot_id=bot_id)
            except CLIENT.exceptions.PreconditionFailedException:
                LOGGER.info("Bot does not exist - bot_id: %s", bot_id)

        return

    if resource_type == "Custom::LexBotVersion":
        # to be implemented - for now keeping versions
        # Use a deletion policy on the resource to avoid attempted deletions
        # on updates: DeletionPolicy: Retain
        return

    if resource_type == "Custom::LexBotAlias":
        bot_alias_id = event["PhysicalResourceId"]
        bot_id = resource_properties["botId"]
        HELPER.Data["botId"] = bot_id
        HELPER.Data["botAliasId"] = bot_alias_id

        if bot_alias_id and bot_id:
            try:
                LEX_CUSTOM_RESOURCE.delete_bot_alias(bot_id=bot_id, bot_alias_id=bot_alias_id)
            except CLIENT.exceptions.PreconditionFailedException:
                LOGGER.info(
                    "Bot alias does not exist - bot_id: %s - bot_alias_id: %s",
                    bot_id,
                    bot_alias_id,
                )

        return

    raise RuntimeError(f"Invalid resource type: {resource_type}")


@HELPER.update
def update_resource(event, _):
    """Update Resource"""
    resource_type = event["ResourceType"]
    resource_properties = event["ResourceProperties"]
    old_resource_properties = event["OldResourceProperties"]

    if resource_type == "Custom::LexBot":
        bot_id = event["PhysicalResourceId"]
        response = LEX_CUSTOM_RESOURCE.update_bot(
            bot_id=bot_id,
            resource_properties=resource_properties,
            old_resource_properties=old_resource_properties,
        )
        HELPER.Data = response

        bot_id = response.get("botId")
        bot_locale_ids = response.get("botLocaleIds")
        _exception = response.get("_exception")
        if bot_id and _exception:
            # This allows to delete the bot if an exception was raised after
            # the bot was created. E.g. while creating the locale, intents, slot, etc.
            HELPER.Status = "FAILED"
            HELPER.Reason = _exception
            HELPER.PhysicalResourceId = bot_id
        else:
            wait_for_bot_locales_build(bot_id=bot_id, bot_locale_ids=bot_locale_ids)

        return bot_id

    if resource_type == "Custom::LexBotVersion":
        # versions are immutable - a new one is created
        response = LEX_CUSTOM_RESOURCE.create_bot_version(resource_properties=resource_properties)
        HELPER.Data = response
        bot_version = response["botVersion"]

        return bot_version

    if resource_type == "Custom::LexBotAlias":
        bot_alias_id = event["PhysicalResourceId"]
        response = LEX_CUSTOM_RESOURCE.update_bot_alias(
            bot_alias_id=bot_alias_id,
            resource_properties=resource_properties,
            old_resource_properties=old_resource_properties,
        )
        HELPER.Data = response

        return response["botAliasId"]

    raise RuntimeError(f"Invalid resource type: {resource_type}")


def handler(event, context):
    """Lambda Handler"""
    HELPER(event, context)