require 'lib/puts_and_flush'
# Polling stack status until it's completed or failed.
class Devops::Executor::StackExecutor
  class StackCreationWaiter
    include PutsAndFlush
    attr_reader :out, :stack

    class SyncResult < Devops::Helpers::ResultObject
      set_result_codes(
        ok: 0,
        stack_rolled_back: 1,
        unkown_status: 2,
        timeout: 3,
        error: 5,
        stack_deleted: 6,
        stack_not_found: 7
      )
    end

    def initialize(stack, out)
      @stack, @out = stack, out
      @printed_events = []
      @sync_result = nil
    end

    def wait
      puts_and_flush "Syncing stack '#{stack.id}'..."
      sleep_times.detect do |sleep_time|
        sleep sleep_time
        stack.sync_status_and_events!
        print_new_events

        update_stack_status if stack_status_changed?
        stack_is_already_created_or_failed?
      end

      @sync_result ||= result(:timeout)
      print_result_message
      @sync_result
    rescue StandardError => e
      DevopsLogger.logger.error e.message
      puts_and_flush "Error: #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
      result(:error)
    end

    private

    def stack_is_already_created_or_failed?
      case stack.stack_status
      when 'CREATE_IN_PROGRESS', 'ROLLBACK_IN_PROGRESS', 'DELETE_IN_PROGRESS'
        false
      when 'CREATE_COMPLETE', 'ROLLBACK_COMPLETE', 'DELETE_COMPLETE', 'CREATE_FAILED', 'NOT_FOUND'
        @sync_result = result_for_provider_status(stack.stack_status)
      else
        @sync_result = result(:unkown_status)
      end
    end

    def sleep_times
      [5]*5 + [10]*400
    end

    def result(reason)
      SyncResult.from_reason(reason)
    end

    def result_for_provider_status(status)
      provider_status_mapping = {
        'CREATE_COMPLETE' => :ok,
        'ROLLBACK_COMPLETE' => :stack_rolled_back,
        'DELETE_COMPLETE' => :stack_deleted,
        'NOT_FOUND' => :stack_not_found
      }
      result(provider_status_mapping.fetch(status))
    end

    def print_result_message
      puts_and_flush Devops::Messages.t("stack_executor.stack_creation_waiter.result.#{@sync_result.reason}",
        stack_id: stack.id, status: stack.stack_status, seconds: sleep_times.inject(&:+))
    end

    def update_stack_status
      stack.save
      @last_status = stack.stack_status
    end

    def stack_status_changed?
      @last_status != stack.stack_status
    end

    def print_new_events
      stack.events.each do |event|
        unless @printed_events.include?(event["event_id"])
          @printed_events << event["event_id"]
          out.puts "#{event["timestamp"]} - #{event["status"]}: #{event["reason"]}"
        end
      end
      out.flush
    end
  end
end
