# frozen_string_literal: true

require 'aws-sdk-s3'
require 'aws-sdk-sns'
require 'concurrent/timer_task'

module Twitch
  module Audit

    # Submits message batches to the service on a timer.
    class Worker

      # How do we tune this worker
      # @return [Twitch::Audit::ClientConfiguration]
      attr_reader :config

      # Our work queue
      # @return [Concurrent::Array]
      attr_reader :queue

      # The timed task that will process batches
      # @return [Concurrent::TimerTask]
      attr_reader :task

      # Working set of entries currently being processed
      # @return [Array]
      attr_accessor :working_set

      # @param [Twitch::Audit::ClientConfiguration] config
      # @param [Concurrent::Array] queue
      def initialize(config, queue)
        @config = config
        @queue = queue
        @working_set = []

        @task = Concurrent::TimerTask.new(
          execution_interval: config.submission_interval,
          timeout_interval: config.submission_timeout
        ) { process_batch }

        @task.execute
      end

      # Processes a batch of items from the queue
      # @return [void]
      def process_batch
        pending_count = queue.length + working_set.length
        return if pending_count.zero?

        # TODO: working set could be stuck with elements in it forever if batch timeouts persisted

        self.working_set = queue.pop(config.submission_batch_size) if working_set.empty?
        working_set.delete_if { |entry| process_entry(entry) }
      end

      # Processes and submits a single entry
      # @param [Twitch::Audit::LogEntry] entry
      # @return [Boolean]
      def process_entry(entry)
        sns_publish(entry.to_json)
        true
      rescue Aws::SNS::Errors::ServiceError => err
        puts "Failed to write entry to sns: #{err.message}"
        handle_dead_entry(entry)
      rescue StandardError => err
        puts "#{err.class} #{err.message}"
        raise err
      end

      private

      # Attempts to write a dead entry to the dead letter s3 bucket
      # @param [Twitch::Audit::LogEntry] entry
      # @return [Boolean]
      def handle_dead_entry(entry)
        puts 'Attempting to write dead letter to s3'
        key = "#{entry.created}-#{entry.service}-#{entry.id}"
        s3_client.put_object(bucket: config.dead_letter_bucket, key: key, body: entry.to_json, content_type: 'application/json')
        true
      rescue Aws::S3::Errors::ServiceError => err
        puts "Failed to write dead letter to s3: #{err.message}"
        puts "Failed payload: #{entry.to_json}"
        true
      rescue StandardError => err
        puts "#{err.class} #{err.message}"
        raise err
      end

      # @param [Object] options additional options to pass into the client constructor
      # @return [Aws::SNS::Topic]
      def sns_client(options = {})
        opts = {
          retry_limit: 10,
          retry_jitter: :full
        }.merge(options)

        @sns_client ||= Aws::SNS::Client.new(opts)
      end

      def sns_publish(message)
        puts 'Attempting to write entry to sns'
        sns_client.publish(topic_arn: config.sns_topic_arn, message: message)
      end

      # @param [Object] options additional options to pass into the client constructor
      # @return [Aws::S3::Bucket]
      def s3_client(options = {})
        @s3_client ||= Aws::S3::Client.new(options)
      end
    end
  end
end
