# frozen_string_literal: true

class ::Rack::MiniProfiler::SnapshotsTransporter
  @@transported_snapshots_count = 0
  @@successful_http_requests_count = 0
  @@failed_http_requests_count = 0

  class << self
    def transported_snapshots_count
      @@transported_snapshots_count
    end

    def successful_http_requests_count
      @@successful_http_requests_count
    end

    def failed_http_requests_count
      @@failed_http_requests_count
    end

    def transport(snapshot)
      @transporter ||= self.new(Rack::MiniProfiler.config)
      @transporter.ship(snapshot)
    end
  end

  attr_reader :buffer
  attr_accessor :max_buffer_size, :gzip_requests

  def initialize(config)
    @uri = URI(config.snapshots_transport_destination_url)
    @auth_key = config.snapshots_transport_auth_key
    @gzip_requests = config.snapshots_transport_gzip_requests
    @thread = nil
    @thread_mutex = Mutex.new
    @buffer = []
    @buffer_mutex = Mutex.new
    @max_buffer_size = 100
    @consecutive_failures_count = 0
    @testing = false
  end

  def ship(snapshot)
    @buffer_mutex.synchronize do
      @buffer << snapshot
      @buffer.shift if @buffer.size > @max_buffer_size
    end
    @thread_mutex.synchronize { start_thread }
  end

  def flush_buffer
    buffer_content = @buffer_mutex.synchronize do
      @buffer.dup if @buffer.size > 0
    end
    if buffer_content
      headers = {
        'Content-Type' => 'application/json',
        'Mini-Profiler-Transport-Auth' => @auth_key
      }
      json = { snapshots: buffer_content }.to_json
      body = if @gzip_requests
        require 'zlib'
        io = StringIO.new
        gzip_writer = Zlib::GzipWriter.new(io)
        gzip_writer.write(json)
        gzip_writer.close
        headers['Content-Encoding'] = 'gzip'
        io.string
      else
        json
      end
      request = Net::HTTP::Post.new(@uri, headers)
      request.body = body
      http = Net::HTTP.new(@uri.hostname, @uri.port)
      http.use_ssl = @uri.scheme == 'https'
      res = http.request(request)
      if res.code.to_i == 200
        @@successful_http_requests_count += 1
        @@transported_snapshots_count += buffer_content.size
        @buffer_mutex.synchronize do
          @buffer -= buffer_content
        end
        @consecutive_failures_count = 0
      else
        @@failed_http_requests_count += 1
        @consecutive_failures_count += 1
      end
    end
  end

  def requests_interval
    [30 + backoff_delay, 60 * 60].min
  end

  private

  def backoff_delay
    return 0 if @consecutive_failures_count == 0
    2**@consecutive_failures_count
  end

  def start_thread
    return if @thread&.alive? || @testing
    @thread = Thread.new do
      while true
        sleep requests_interval
        flush_buffer
      end
    end
  end
end
