class ElasticAPM::CentralConfig

@api private

def assign(update)

def assign(update)
  # For each updated option, store the original value,
  # unless already stored
  update.each_key do |key|
    @modified_options[key] ||= config.get(key.to_sym)&.value
  end
  # If the new update doesn't set a previously modified option,
  # revert it to the original
  @modified_options.each_key do |key|
    next if update.key?(key)
    update[key] = @modified_options.delete(key)
  end
  update_config(update)
end

def fetch_and_apply_config

def fetch_and_apply_config
  @promise =
    Concurrent::Promise
    .execute(&method(:fetch_config))
    .on_success(&method(:handle_success))
    .rescue(&method(:handle_error))
end

def fetch_config

def fetch_config
  resp = perform_request
  case resp.status
  when 200..299
    resp
  when 300..399
    resp
  when 400..499
    raise ClientError, resp
  when 500..599
    raise ServerError, resp
  end
end

def handle_error(error)

def handle_error(error)
  # For tests, WebMock failures don't have real responses
  response = error.response if error.respond_to?(:response)
  debug(
    'Failed fetching config: %s, trying again in %d seconds',
    response&.body, DEFAULT_MAX_AGE
  )
  assign({})
  schedule_next_fetch(response)
end

def handle_success(resp)

rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
def handle_success(resp)
  if (etag = resp.headers['Etag'])
    @etag = etag
  end
  if resp.status == 304
    info 'Received 304 Not Modified'
  else
    if resp.body && !resp.body.empty?
      update = JSON.parse(resp.body.to_s)
      assign(update)
    end
    if @modified_options.any?
      info 'Updated config from Kibana'
      debug 'Modified: %s', @modified_options.inspect
    end
  end
  schedule_next_fetch(resp)
  true
rescue Exception => e
  error 'Failed to apply remote config, %s', e.inspect
  debug { e.backtrace.join('\n') }
end

def headers

def headers
  { 'Etag': @etag }
end

def initialize(config)

def initialize(config)
  @config = config
  @modified_options = {}
  @http = Transport::Connection::Http.new(config)
  @etag = 1
end

def perform_request

def perform_request
  @http.get(server_url, headers: headers)
end

def schedule_next_fetch(resp = nil)

def schedule_next_fetch(resp = nil)
  headers = resp&.headers
  seconds =
    if headers['Cache-Control']
      CacheControl.new(headers['Cache-Control']).max_age
    else
      DEFAULT_MAX_AGE
    end
  @scheduled_task =
    Concurrent::ScheduledTask
    .execute(seconds, &method(:fetch_and_apply_config))
end

def server_url

def server_url
  @server_url ||=
    config.server_url +
    '/config/v1/agents' \
    "?service.name=#{config.service_name}"
end

def start

def start
  return unless config.central_config?
  debug 'Starting CentralConfig'
  fetch_and_apply_config
end

def stop

def stop
  debug 'Stopping CentralConfig'
  @scheduled_task&.cancel
end

def update_config(new_options)

def update_config(new_options)
  @config = config.dup.tap { |new_config| new_config.assign(new_options) }
end