# frozen_string_literal: true

require 'litany/cli/project_base'

require 'litany/project'

module Litany
  module CLI
    # Compiles the current Litany workspace
    class Upload < Litany::CLI::ProjectBase
      option ['-S', '--stack'], 'STACK NAME', 'If provided the upload operation will only target the stack name provided.', multivalued: true, default: []

      def execute
        matching_stacks.each do |project, stack_name|
          if can_upload_stack?(project, stack_name)
            next if stack_exist?(project, stack_name) ? update_stack(project, stack_name) : create_stack(project, stack_name)
          else
            warn "Stack Name: #{stack_name}, Stage: #{project.stage}, Environment: #{project.active_environment.name} is not in an uploadable state. Skipping..."
          end

          break info('Aborting...') unless agree('Do you wish to continue?', default: false)
        end
      end

      # Creates a new stack in AWS
      # @param project [Project] the project which the stack belongs to
      # @param stack_name [Symbol] internal name of the stack which is being changed
      # @return [Boolean]
      def create_stack(project, stack_name)
        cfn_client = project.cfn_client

        info "Submitting a new stack.  Name: #{stack_name}, Stage: #{project.stage}, Environment: #{project.active_environment.name}"

        output = project.output(stack_name)
        cfn_client.create_stack(stack_name: stack_name, template_body: output, capabilities: ['CAPABILITY_NAMED_IAM'])

        return true unless agree('Do you wish to wait for the stack creation to complete?'.indent(1), default: false)

        begin
          cfn_client.wait_until(:stack_create_complete, stack_name: stack_name) do |waiter|
            waiter.before_attempt { |step| waiting(step) }
            waiter.delay = 10
            waiter.max_attempts = 180
          end
        rescue Aws::Waiters::Errors::FailureStateError => e
          cli.newline
          error 'The stack creation has failed.'
          error e.message.indent(1)
          return false
        rescue Aws::Waiters::Errors::UnexpectedError => e
          cli.newline
          error 'Unexpected exception occurred while waiting for the stack creation to complete.'
          error e.message.indent(1)
          return false
        end

        say 'Stack creation complete!'

        true
      end

      # Updates an existing stack
      # @param project [Project] the project which the stack belongs to
      # @param stack_name [Symbol] internal name of the stack which is being changed
      # @return [Boolean]
      def update_stack(project, stack_name)
        change_set_name = submit_change_set(project, stack_name, auto_delete: false)
        change_set_name.nil? || execute_change_set(project, stack_name, change_set_name)
      end

      # Executes a specific change set for execution.
      # @param project [Project] the project which the stack belongs to
      # @param stack_name [Symbol] internal name of the stack which is being changed
      # @param change_set_name [String] reference name of the change set
      def execute_change_set(project, stack_name, change_set_name)
        cfn_client = project.cfn_client

        unless agree('Do you wish to execute this change set?', default: false)
          info 'Skipping this change set...'
          cfn_client.delete_change_set(stack_name: stack_name, change_set_name: change_set_name)
          return false
        end

        info 'Executing right away!'
        cfn_client.execute_change_set(stack_name: stack_name, change_set_name: change_set_name, client_request_token: change_set_name)

        return true unless agree('Do you wish to wait for the stack update to complete?'.indent(1), default: false)

        begin
          cfn_client.wait_until(:stack_update_complete, stack_name: stack_name) do |waiter|
            waiter.before_attempt { |step| waiting(step) }
            waiter.delay = 10
            waiter.max_attempts = 180
          end
        rescue Aws::Waiters::Errors::FailureStateError => e
          cli.newline
          error 'The stack update has failed.'
          error e.message.indent(1)
          return false
        rescue Aws::Waiters::Errors::UnexpectedError => e
          cli.newline
          error 'Unexpected exception occurred while waiting for the stack to complete updating.'
          error e.message.indent(1)
          return false
        end

        say 'Stack update complete!'

        true
      end

      # Tests to see if a stack is currently in a state in AWS that allows us to upload to it.
      # @param project [Project] the project which the stack belongs to
      # @param stack_name [Symbol] internal name of the stack which is being changed
      # @return [Boolean]
      def can_upload?(project, stack_name)
        statuses = project.cfn_client.describe_stacks(stack_name: stack_name).stacks.collect(&:stack_status)

        if statuses.length > 1
          error "Two stacks detected with the same name, this shouldn't happen with Litany."
          return false
        end

        !statuses[0].end_with?('PROGRESS')
      rescue Aws::CloudFormation::Errors::ValidationError => e
        e.message.include?('does not exist')
      end
    end
  end
end
