"""
Copyright 2017-present Airbnb, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from fnmatch import fnmatch
import json
import os

from streamalert.shared.config import firehose_alerts_bucket
from streamalert.shared.logger import get_logger
from streamalert.shared.utils import get_data_file_format
from streamalert_cli.athena.handler import create_table, create_log_tables
from streamalert_cli.helpers import check_credentials, continue_prompt, run_command
from streamalert_cli.manage_lambda.deploy import deploy
from streamalert_cli.terraform.generate import terraform_generate_handler
from streamalert_cli.terraform.helpers import terraform_check, terraform_runner
from streamalert_cli.utils import (
    add_clusters_arg,
    CLICommand,
    set_parser_epilog,
    UniqueSortedListAction,
)

LOGGER = get_logger(__name__)


class TerraformInitCommand(CLICommand):
    description = 'Initialize StreamAlert infrastructure'

    @classmethod
    def setup_subparser(cls, subparser):
        """Manage.py init takes no arguments"""

    @classmethod
    def handler(cls, options, config):
        """Initialize infrastructure using Terraform

        Args:
            config (CLIConfig): Loaded StreamAlert config

        Returns:
            bool: False if errors occurred, True otherwise
        """
        LOGGER.info('Initializing StreamAlert')

        # generate init Terraform files
        if not terraform_generate_handler(config=config, init=True):
            return False

        LOGGER.info('Initializing Terraform')
        if not run_command(['terraform', 'init'], cwd=config.build_directory):
            return False

        # build init infrastructure
        LOGGER.info('Building initial infrastructure')
        init_targets = [
            'aws_s3_bucket.lambda_source', 'aws_s3_bucket.logging_bucket',
            'aws_s3_bucket.streamalert_secrets', 'aws_s3_bucket.terraform_remote_state',
            'aws_s3_bucket.streamalerts',
            'aws_kms_key.server_side_encryption', 'aws_kms_alias.server_side_encryption',
            'aws_kms_key.streamalert_secrets', 'aws_kms_alias.streamalert_secrets',
            'module.streamalert_athena', #required for the alerts table
            'aws_dynamodb_table.terraform_remote_state_lock'
        ]

        # this bucket must exist before the log tables can be created, but
        # shouldn't be created unless the firehose is enabled
        if config['global']['infrastructure'].get('firehose', {}).get('enabled'):
            init_targets.append('aws_s3_bucket.streamalert_data')

        if not terraform_runner(config, targets=init_targets):
            LOGGER.error('An error occurred while running StreamAlert init')
            return False

        # generate the main.tf with remote state enabled
        LOGGER.info('Configuring Terraform Remote State')
        if not terraform_generate_handler(config=config, check_tf=False, check_creds=False):
            return False

        if not run_command(['terraform', 'init'], cwd=config.build_directory):
            return False

        LOGGER.info('Deploying Lambda Functions')

        functions = ['rule', 'alert', 'alert_merger', 'athena', 'classifier']

        deploy(config, functions)

        # we need to manually create the streamalerts table since terraform does not support this
        # See: https://github.com/terraform-providers/terraform-provider-aws/issues/1486
        if get_data_file_format(config) == 'json':
            # Terraform v0.12 now supports creating Athena tables. We will support
            # to use terraform aws_glue_catalog_table resource to create table only
            # when data file_format is set to "parquet" in "athena_partitioner_config"
            #
            # For "json" file_format, we will continue using Athena DDL query to
            # create tables. However, this capabity will be faded out in the future
            # release because we want users to take advantage of parquet performance.
            alerts_bucket = firehose_alerts_bucket(config)
            create_table('alerts', alerts_bucket, config)

            # Create the glue catalog tables for the enabled logs
            if not create_log_tables(config=config):
                return

        LOGGER.info('Building remaining infrastructure')
        return terraform_runner(config, refresh=False)


class TerraformBuildCommand(CLICommand):
    description = 'Run terraform against StreamAlert modules, optionally targeting specific modules'

    @classmethod
    def setup_subparser(cls, subparser):
        """Add build subparser: manage.py build [options]"""
        set_parser_epilog(
            subparser,
            epilog=(
                '''\
                Example:

                    manage.py build --target alert_processor_lambda
                '''
            )
        )

        _add_default_tf_args(subparser, add_cluster_args=False)

    @classmethod
    def handler(cls, options, config):
        """Run Terraform with an optional set of targets and clusters

        Args:
            options (argparse.Namespace): Parsed arguments from manage.py
            config (CLIConfig): Loaded StreamAlert config

        Returns:
            bool: False if errors occurred, True otherwise
        """
        if not terraform_generate_handler(config=config):
            return False

        # Will create log tables only when file_format set to "json" and return erlier if
        # log tables creation failed.
        # This capabity will be faded out in the future release.
        if get_data_file_format(config) == 'json' and not create_log_tables(config=config):
            return

        target_modules, valid = _get_valid_tf_targets(config, options.target)
        if not valid:
            return False

        return terraform_runner(config, targets=target_modules if target_modules else None)


class TerraformDestroyCommand(CLICommand):
    description = 'Destroy StreamAlert infrastructure, optionally targeting specific modules'

    @classmethod
    def setup_subparser(cls, subparser):
        """Add destroy subparser: manage.py destroy [options]"""
        set_parser_epilog(
            subparser,
            epilog=(
                '''\
                Example:

                    manage.py destroy --target aws_s3_bucket-streamalerts
                '''
            )
        )

        _add_default_tf_args(subparser)

    @classmethod
    def handler(cls, options, config):
        """Use Terraform to destroy any existing infrastructure

        Args:
            options (argparse.Namespace): Parsed arguments from manage.py
            config (CLIConfig): Loaded StreamAlert config

        Returns:
            bool: False if errors occurred, True otherwise
        """
        # Check for valid credentials
        if not check_credentials():
            return False

        # Verify terraform is installed
        if not terraform_check():
            return False

        # Ask for approval here since multiple Terraform commands may be necessary
        if not continue_prompt(message='Are you sure you want to destroy?'):
            return False

        if options.target:
            target_modules, valid = _get_valid_tf_targets(config, options.target)
            if not valid:
                return False

            return terraform_runner(
                config,
                destroy=True,