class ZuoraConnect::AppInstanceBase

def new_session(session: self.data_lookup, username: self.access_token, password: self.refresh_token, holding_pattern: false, **args)

def new_session(session: self.data_lookup, username: self.access_token, password: self.refresh_token, holding_pattern: false, **args)
  self.api_version = "v2"
  self.username = username
  self.password = password
  self.last_refresh = session["#{self.id}::last_refresh"]
  self.connect_user = session["#{self.id}::user::email"] if session["#{self.id}::user::email"].present?
  PaperTrail.whodunnit = self.connect_user if defined?(PaperTrail)
  ElasticAPM.set_user(self.connect_user)   if defined?(ElasticAPM) && ElasticAPM.running?
  recoverable_session = false
  ## DEV MODE TASK DATA MOCKUP
  if ZuoraConnect.configuration.mode != "Production"
    mock_task_data = {
      "id" => ZuoraConnect.configuration.dev_mode_appinstance,
      "mode" => ZuoraConnect.configuration.dev_mode_mode
    }
    case ZuoraConnect.configuration.dev_mode_options.class
    when Hash
      self.options = ZuoraConnect.configuration.dev_mode_options
    when Array
      mock_task_data["options"] = ZuoraConnect.configuration.dev_mode_options
    end
    ZuoraConnect.configuration.dev_mode_logins.each do |k,v|
      v = v.merge({"entities": [] }) if !v.keys.include?("entities")
      mock_task_data[k] = v
    end
    self.build_task(task_data: mock_task_data, session: session)
    self.last_refresh = Time.now.to_i
  else
    time_expire = (session["#{self.id}::last_refresh"] || Time.now).to_i - INSTANCE_REFRESH_WINDOW.ago.to_i
    if session.empty?
      self.new_session_message = "REFRESHING - Session Empty"
      ZuoraConnect.logger.debug(self.new_session_message)
      raise ZuoraConnect::Exceptions::HoldingPattern if holding_pattern && !self.mark_for_refresh
      self.refresh(session: session)
    elsif (self.id != session["appInstance"].to_i)
      self.new_session_message = "REFRESHING - AppInstance ID(#{self.id}) does not match session id(#{session["appInstance"].to_i})"
      ZuoraConnect.logger.debug(self.new_session_message)
      raise ZuoraConnect::Exceptions::HoldingPattern if holding_pattern && !self.mark_for_refresh
      self.refresh(session: session)
    elsif session["#{self.id}::task_data"].blank?
      self.new_session_message = "REFRESHING - Task Data Blank"
      ZuoraConnect.logger.debug(self.new_session_message)
      raise ZuoraConnect::Exceptions::HoldingPattern if holding_pattern && !self.mark_for_refresh
      self.refresh(session: session)
    elsif session["#{self.id}::last_refresh"].blank?
      self.new_session_message = "REFRESHING - No Time on Cookie"
      recoverable_session = true
      ZuoraConnect.logger.debug(self.new_session_message)
      raise ZuoraConnect::Exceptions::HoldingPattern if holding_pattern && !self.mark_for_refresh
      self.refresh(session: session)
    # If the cache is expired and we can aquire a refresh lock
    elsif (session["#{self.id}::last_refresh"].to_i < INSTANCE_REFRESH_WINDOW.ago.to_i) && self.mark_for_refresh
      self.new_session_message = "REFRESHING - Session Old by #{time_expire.abs} second"
      recoverable_session = true
      ZuoraConnect.logger.debug(self.new_session_message)
      self.refresh(session: session)
    else
      if time_expire < 0
        self.new_session_message = ["REBUILDING - Expired by #{time_expire} seconds", self.marked_for_refresh? ? " cache updating as of #{self.reset_mark_refreshed_at} seconds ago" : nil].compact.join(',')
      else
        self.new_session_message = "REBUILDING - Expires in #{time_expire} seconds"
      end
      ZuoraConnect.logger.debug(self.new_session_message)
      self.build_task(task_data: session["#{self.id}::task_data"], session: session)
    end
  end
  return self
rescue ZuoraConnect::Exceptions::HoldingPattern => ex
  while self.marked_for_refresh?
    ZuoraConnect.logger.info("Holding - Expires in #{self.reset_mark_expires_at}. '#{self.new_session_message}'")
    sleep(HOLDING_PATTERN_SLEEP)
  end
  self.reload_attributes([:refresh_token, :oauth_expires_at, :access_token])
  session = self.data_lookup(session: session)
  retry
rescue ZuoraConnect::Exceptions::MissMatch => ex
  self.delete_app_instance
  session = {}
  ZuoraConnect.logger.error(ex, app_instance_id_new: self.task_data['id'])
  retry
rescue ZuoraConnect::Exceptions::InvalidCredentialSet => ex
  raise
rescue => ex
  if recoverable_session
    ZuoraConnect.logger.warn("REBUILDING - Using backup expired cache", ex, self.default_ougai_items)
    self.build_task(task_data: session["#{self.id}::task_data"], session: session)
    return self
  else
    ZuoraConnect.logger.error("Failed new session", ex)
    raise
  end
ensure
  begin
    I18n.locale = self.locale
  rescue I18n::InvalidLocale => ex
    ZuoraConnect.logger.error(ex) if !IGNORED_LOCALS.include?(ex.locale.to_s.downcase)
  end
  Time.zone = self.timezone
  if self.task_data.present?
    tenants = self.task_data.fetch('tenant_ids', [])
    organizations = self.task_data.fetch('organizations', [])
    if defined?(ElasticAPM) && ElasticAPM.running?
      if ElasticAPM.respond_to?(:set_label)
        ElasticAPM.set_label(:tenant_id, tenants.first)
        ElasticAPM.set_label(:organization, organizations.first)
      else
        ElasticAPM.set_label(:tenant_id, tenants.first)
        ElasticAPM.set_label(:organization, organizations.first)
      end
    end
    params = {
      name: self.task_data.dig('name'), 
      zuora_entity_ids: (self.task_data.dig(LOGIN_TENANT_DESTINATION,'entities') || []).map{|e| e['id']}.uniq,
      zuora_tenant_ids: tenants.map(&:to_s).uniq,
      organizations: organizations
    }
    if self.methods.include?(LOGIN_TENANT_DESTINATION.to_sym)
      client = self.send(LOGIN_TENANT_DESTINATION).client
      if defined?(client.rest_domain)
        ZuoraConnect::RequestIdMiddleware.zuora_rest_domain = client.rest_domain
        params.merge!({zuora_domain: client.rest_domain, environment: client.environment }) 
      end
    end
    params = params.reject{|k,v| !self.attributes.keys.member?(k.to_s) || self[k] == v}
    self.update_columns(params) if params.present?
  end
end