module ZuoraConnect::Controllers::Helpers

def authenticate_app_api_request

def authenticate_app_api_request
  ZuoraConnect::AppInstance.read_master_db do
    #Skip session for api requests
    request.session_options[:skip] = true
    Thread.current[:appinstance] = nil
    if ZuoraConnect.logger.is_a?(Ougai::Logger); ZuoraConnect.logger.with_fields = {}; end
    if Rails.logger.is_a?(Ougai::Logger); Rails.logger.with_fields = {}; end
    if defined?(ElasticAPM) && ElasticAPM.running?
      if ElasticAPM.respond_to?(:set_label)
        ElasticAPM.set_label(:trace_id, request.uuid) if defined?(ElasticAPM) && ElasticAPM.running?
      else
        ElasticAPM.set_label(:trace_id, request.uuid) if defined?(ElasticAPM) && ElasticAPM.running?
      end
    end
    if request.headers["API-Token"].present?
      @appinstance = ZuoraConnect::AppInstance.find_by(:api_token => request.headers["API-Token"])
      ZuoraConnect.logger.debug("API REQUEST - API token") if @appinstance.present?
      check_instance
    elsif ZuoraConnect::AppInstance::INTERNAL_HOSTS.include?(request.headers.fetch("HOST", nil))
      zuora_host, zuora_entity_id, zuora_instance_id = [request.headers['zuora-host'], (request.headers['zuora-entity-ids'] || "").gsub('-',''), request.headers['zuora-instance-id']]
      #Validate host present
      if zuora_host.blank?
        render json: {"status": 401, "message": "zuora-host header was not supplied."}, status: :unauthorized
        return
      end
      #Validate entity-ids present
      if zuora_entity_id.blank?
        render json: {"status": 401, "message": "zuora-entity-ids header was not supplied."}, status: :unauthorized
        return
      end
      #Select with instance id if present. Used where mulitple deployments are done.
      if zuora_instance_id.present?
        appinstances = ZuoraConnect::AppInstance.where("zuora_entity_ids ?& array[:entities] = true AND zuora_domain = :host AND id = :id", entities: [zuora_entity_id], host: zuora_host, id: zuora_instance_id.to_i)
      else
        appinstances = ZuoraConnect::AppInstance.where("zuora_entity_ids ?& array[:entities] = true AND zuora_domain = :host", entities: [zuora_entity_id], host: zuora_host)
      end
      if appinstances.size == 0
        render json: {"status": 401, "message": "Missing mapping or no deployment for '#{zuora_host}-#{zuora_entity_id}' ."}, status: :unauthorized
        return
      elsif appinstances.size > 1
        render json: {"status": 401, "message": "More than one app instance binded to host and entity ids. Please indicate correct instance via 'zuora-instance-id' header", "instances": appinstances.map {|instance| instance.id }.sort }, status: :unauthorized
        return
      else
        @appinstance = appinstances.first
        check_instance
      end
    elsif request.headers.fetch("Authorization", "").include?("Basic ")
      authenticate_or_request_with_http_basic do |username, password|
        @appinstance = ZuoraConnect::AppInstance.find_by(:token => password)
        @appinstance ||= ZuoraConnect::AppInstance.find_by(:api_token => password)
        ZuoraConnect.logger.debug("API REQUEST - Basic Auth") if @appinstance.present?
        check_instance
      end
    else
      check_instance
    end
  end
end

def authenticate_connect_app_request

def authenticate_connect_app_request
  ZuoraConnect::AppInstance.read_master_db do
    Thread.current[:appinstance] = nil
    if ZuoraConnect.logger.is_a?(Ougai::Logger); ZuoraConnect.logger.with_fields = {}; end
    if Rails.logger.is_a?(Ougai::Logger); Rails.logger.with_fields = {}; end
    if defined?(ElasticAPM) && ElasticAPM.running?
      if ElasticAPM.respond_to?(:set_label)
        ElasticAPM.set_label(:trace_id, request.uuid)
      else
        ElasticAPM.set_label(:trace_id, request.uuid)
      end
    end
    if ZuoraConnect.configuration.mode == "Production"
      setup_instance_via_prod_mode
    else
      setup_instance_via_dev_mode
    end
    return if performed?
    if !defined?(@appinstance) || @appinstance.blank?
      render "zuora_connect/static/error_handled", :locals => {
        :title => "Application state could not be found.",
        :message => "Please relaunch application."
      }, :layout => false
      return
    end
    #Call .data_lookup with the current session to retrieve session. In some cases session may be stored/cache in redis
    #so data lookup provides a model method that can be overriden per app.
    if params[:controller] != 'zuora_connect/api/v1/app_instance' && params[:action] != 'drop'
      if @appinstance.new_session_for_ui_requests(:params => params)
        @appinstance.new_session(:session => @appinstance.data_lookup(:session => session))
      end
    end
    if session["#{@appinstance.id}::user::email"].present?
      ElasticAPM.set_user(session["#{@appinstance.id}::user::email"])  if defined?(ElasticAPM) && ElasticAPM.running?
      PaperTrail.whodunnit =  session["#{@appinstance.id}::user::email"] if defined?(PaperTrail)
    end
    locale = (session["#{@appinstance.id}::user::locale"] || "").gsub("_", "-")
    begin
      I18n.locale = locale.present? ? locale : @appinstance.locale
    rescue I18n::InvalidLocale => ex
      if locale.include?("-")
        locale = locale.split("-").first
        retry
      elsif locale != session["#{@appinstance.id}::user::language"]
        locale = session["#{@appinstance.id}::user::language"]
        retry
      end
      ZuoraConnect.logger.error(ex) if !ZuoraConnect::AppInstance::IGNORED_LOCALS.include?(ex.locale.to_s.downcase)
    end
    if @appinstance.user_timezone.blank?
      @appinstance.set_timezone(timezone: session["#{@appinstance.id}::user::timezone"], type: :default)
    end
  end
rescue ZuoraConnect::Exceptions::InvalidCredentialSet => ex
  id = @appinstance.id
  ZuoraConnect::AppInstance.destroy(id)
  Apartment::Tenant.drop(id)
  render "zuora_connect/static/error_handled", :locals => {
    :title => "Application Setup Error",
    :message => "Application cannot be run using Zuora Session. Delete old application \
    deployment and create new with Zuora Basic or OAuth credentials."
  }, :layout => false
  return
rescue ZuoraConnect::Exceptions::AccessDenied => ex
  respond_to do |format|
    format.html {
      render "zuora_connect/static/error_handled", :locals => {
          :title => "Application State Error",
          :message => ex.message
        }, status: 401, layout: false
    }
    format.js {
      render "zuora_connect/static/error_handled", :locals => {
          :title => "Application State Error",
          :message => ex.message
        }, status: 401, layout: false
    }
    format.json { render json: {'errors' => ex.message}, status: 401 }
    format.all { render json: ex.message, status: 401 }
  end
  return
rescue => ex
  ZuoraConnect.logger.error("UI Authorization Error", ex)
  respond_to do |format|
    format.html { render 'zuora_connect/static/error_unhandled', :locals => {:exception => ex, :skip_exception => true}, status: 500, layout: false }
    format.js { render 'zuora_connect/static/error_unhandled', :locals => {:exception => ex, :skip_exception => true}, status: 500, layout: false}
  end
  return
end

def check_connect_admin

def check_connect_admin
  return session["#{@appinstance.id}::admin"]
end

def check_connect_admin!(raise_error: false)

def check_connect_admin!(raise_error: false)
  if !session["#{@appinstance.id}::admin"]
    raise ZuoraConnect::Exceptions::AccessDenied.new("User is not an authorized admin for this application") if raise_error
    respond_to do |format|
      format.html {
        render "zuora_connect/static/error_handled", :locals => {
            :title => "Unauthorized",
            :message => "User is not an authorized admin for this application"
          }, status: 401, :layout => false
      }
      format.js {
        render "zuora_connect/static/error_handled", :locals => {
            :title => "Unauthorized",
            :message => "User is not an authorized admin for this application"
          }, status: 401, :layout => false
      }
      format.json { render json: {'errors' => ex.message}, status: 401 }
      format.all { render json: ex.message, status: 401 }
    end
    return
  end
end

def check_instance

API ONLY
def check_instance
  if defined?(@appinstance) && @appinstance.present?
    if @appinstance.new_session_for_api_requests(:params => params)
      @appinstance.new_session(:session => @appinstance.data_lookup(:session => session))
    end
    Thread.current[:appinstance] = @appinstance
    PaperTrail.whodunnit = "API User" if defined?(PaperTrail)
    ElasticAPM.set_user("API User")  if defined?(ElasticAPM) && ElasticAPM.running?
    return true
  else
    response.set_header('WWW-Authenticate', "Basic realm=\"Application\"")
    render json: {"status": 401, "message": "Access Denied"}, status: :unauthorized
    return false
  end
end

def create_new_instance

def create_new_instance
  ZuoraConnect::AppInstance.read_master_db do
    Thread.current[:appinstance] = nil
    ZuoraConnect.logger.with_fields = {} if ZuoraConnect.logger.is_a?(Ougai::Logger)
    Rails.logger.with_fields = {} if Rails.logger.is_a?(Ougai::Logger)
    if defined?(ElasticAPM) && ElasticAPM.running? && ElasticAPM.respond_to?(:set_label)
      ElasticAPM.set_label(:trace_id, request.uuid)
    end
    zuora_host = request.headers['zuora-host']
    zuora_entity_id = (request.headers['zuora-entity-ids'] || '').gsub(
      '-',
      ''
    ).split(',').first
    # Validate host present
    if zuora_host.blank?
      render json: {
        status: 401,
        message: 'zuora-host header was not supplied.'
      }, status: :unauthorized
      return
    end
    # Validate entity-ids present
    if zuora_entity_id.blank?
      render json: {
        status: 401,
        message: 'zuora-entity-ids header was not supplied.'
      }, status: :unauthorized
      return
    end
    rest_domain = ZuoraAPI::Login.new(url: "https://#{zuora_host}").rest_domain
    app_instance_id = ZuoraConnect::AppInstance.where(
      'zuora_entity_ids ?& array[:entities] AND zuora_domain = :host',
      entities: [zuora_entity_id],
      host: rest_domain
    ).pluck(:id).first
    if app_instance_id.present?
      render json: {
        status: 409,
        message: 'Instance already exists.',
        app_instance_id: app_instance_id
      }, status: 409
    else
      Apartment::Tenant.switch!("public")
      retry_count = 3
      begin
        @appinstance = new_instance(
          next_instance_id,
          zuora_entity_id,
          rest_domain,
          tenant_id: request.headers['zuora-tenant-id'],
          retry_count: retry_count
        )
      rescue ActiveRecord::RecordNotUnique
        retry if (retry_count -= 1).positive?
        return
      end
      app_instance_id = @appinstance.id
    end
    begin
      Apartment::Tenant.switch!('public')
      Apartment::Tenant.create(app_instance_id.to_s)
    rescue Apartment::TenantExists
      ZuoraConnect.logger.debug('Tenant Already Exists')
    end
  end
end

def generate_token

def generate_token
  rand(36**64).to_s(36)
end

def hallway_integration?

def hallway_integration?
  return (request.headers['ZuoraCurrentEntity'].present? || cookies['ZuoraCurrentEntity'].present?)
end

def new_instance(id, zuora_entity_id, rest_domain, tenant_id: nil, task_data: nil, retry_count: 0)

def new_instance(id, zuora_entity_id, rest_domain, tenant_id: nil, task_data: nil, retry_count: 0)
  app_instance = ZuoraConnect::AppInstance.new(
    :id => id,
    :api_token => generate_token,
    :token => generate_token,
    :oauth_expires_at => Time.now + 1000.years,
    :zuora_domain => rest_domain,
    :zuora_entity_ids => [zuora_entity_id]
  )
  app_instance[:zuora_tenant_ids] = [tenant_id.to_s] if tenant_id.present?
  if task_data.nil?
    # no encryption
    app_instance['zuora_logins'] = task_data
  else
    # kms encrypt
    app_instance.zuora_logins = task_data
  end
  begin
    app_instance.save(:validate => false)
  rescue ActiveRecord::RecordNotUnique
    raise if retry_count > 1
    Thread.current[:appinstance] = nil
    session['appInstance'] = nil
    render 'zuora_connect/static/error_handled', :locals => {
      :title => 'Application could not create unique tokens.',
      :message => 'Please contact support or retry launching application.'
    }, :layout => false
    return
  end
  app_instance
end

def next_instance_id

def next_instance_id
  min_instance_id = 24_999_999
  (ZuoraConnect::AppInstance.all.where("id > #{min_instance_id}").order(id: :desc).limit(1).pluck(:id).first || min_instance_id) + 1
end

def persist_connect_app_session

def persist_connect_app_session
  if @appinstance.present?
    if defined?(Redis.current)
      @appinstance.cache_app_instance
    else
      session.merge!(@appinstance.save_data)
    end
  end
end

def setup_instance_via_dev_mode

def setup_instance_via_dev_mode
  session["appInstance"] = ZuoraConnect.configuration.dev_mode_appinstance
  session["#{ZuoraConnect.configuration.dev_mode_appinstance}::admin"] =  ZuoraConnect.configuration.dev_mode_admin
  values = {
    id: ZuoraConnect.configuration.dev_mode_appinstance,
    access_token: ZuoraConnect.configuration.dev_mode_user,
    refresh_token: ZuoraConnect.configuration.dev_mode_pass,
    token: ZuoraConnect.configuration.dev_mode_pass+ZuoraConnect.configuration.dev_mode_pass,
    api_token: ZuoraConnect.configuration.dev_mode_pass+ZuoraConnect.configuration.dev_mode_pass
  }
  @appinstance = ZuoraConnect::AppInstance.find_by(:id => ZuoraConnect.configuration.dev_mode_appinstance.to_i)
  ZuoraConnect::ZuoraUser.current_user_id = 0
  if @appinstance.blank?
    Apartment::Tenant.switch!("public")
    begin
      Apartment::Tenant.create(ZuoraConnect.configuration.dev_mode_appinstance.to_s)
    rescue Apartment::TenantExists => ex
      Apartment::Tenant.drop(ZuoraConnect.configuration.dev_mode_appinstance.to_s)
      retry
    end
    @appinstance = ZuoraConnect::AppInstance.new()
  end
  @appinstance.assign_attributes(values)
  @appinstance.save(:validate => false)
end

def setup_instance_via_prod_mode

def setup_instance_via_prod_mode
  zuora_entity_id = request.headers['ZuoraCurrentEntity'] || cookies['ZuoraCurrentEntity']
  ZuoraConnect::ZuoraUser.current_user_id = nil
  if zuora_entity_id.present?
    zuora_tenant_id = cookies['Zuora-Tenant-Id']
    zuora_user_id = cookies['Zuora-User-Id']
    zuora_host = request.headers['HTTP_X_FORWARDED_HOST'] || request.headers['Zuora-Host'] || 'apisandbox.zuora.com'
    zuora_details = {'host' => zuora_host, 'user_id' => zuora_user_id, 'tenant_id' => zuora_tenant_id, 'entity_id' => zuora_entity_id}
    auth_headers = {}
    #Do we need to refresh session identity
    if request.headers["Zuora-Auth-Token"].present?
      zuora_client = ZuoraAPI::Oauth.new(url: "https://#{zuora_host}", bearer_token: request.headers["Zuora-Auth-Token"], entity_id: zuora_entity_id, oauth_session_expires_at: Time.now + 5.minutes )
    elsif cookies['ZSession'].present?
      zuora_client = ZuoraAPI::Basic.new(url: "https://#{zuora_host}", session: cookies['ZSession'], entity_id: zuora_entity_id)
      auth_headers.merge!({'Authorization' => "ZSession-a3N2w #{zuora_client.get_session(prefix: false, auth_type: :basic)}"})
    else
      render "zuora_connect/static/error_handled", :locals => {
        :title => "Missing Authorization Token",
        :message => "Zuora 'Zuora-Auth-Token' header and 'ZSession' cookie not present."
      }, :layout => false
      return
    end
    begin
      zuora_instance_id = params[:sidebar_launch].to_s.to_bool ? nil : (params[:app_instance_id] || session["appInstance"])
      #Identity blank or current entity different
      different_zsession = session["ZSession"] != cookies['ZSession']
      missmatched_entity = session["ZuoraCurrentEntity"] != zuora_entity_id
      missing_identity = session["ZuoraCurrentIdentity"].blank?
      if (missing_identity || missmatched_entity || different_zsession)
        zuora_details.merge!({'identity' => {'different_zsession' => different_zsession, 'missing_identity' => missing_identity, 'missmatched_entity' => missmatched_entity}})
        identity, response = zuora_client.rest_call(url: zuora_client.rest_endpoint("identity"))
        if zuora_entity_id != identity['entityId']
          if zuora_tenant_id.to_s == "10548"
            session.clear
            render "zuora_connect/static/error_handled", :locals => {
              :title => "Security Testing",
              :message => "Ya we know it you"
            }, :layout => false
            return
          else
            raise ZuoraConnect::Exceptions::Error.new("Header entity id does not match identity call entity id.")
          end
        end
        ##
        # If the ZSession was refreshed, but it's still the same user and they aren't launching from the side bar,
        # we don't need to continue
        is_different_user = identity.slice("entityId", "tenantId", "userId", "userProfileId") != (session["ZuoraCurrentIdentity"] || {}).slice("entityId", "tenantId", "userId", "userProfileId")
        zuora_details["identity"]["entityId"] = identity['entityId']
        session["ZuoraCurrentIdentity"] = identity
        session["ZuoraCurrentEntity"] = identity['entityId']
        session["ZSession"] = cookies['ZSession']
        if is_different_user || params[:sidebar_launch].to_s.to_bool
          zuora_instance_id = nil
          ZuoraConnect.logger.debug("UI Authorization", zuora: zuora_details)
          client_describe, response = zuora_client.rest_call(
              url: zuora_client.rest_endpoint("genesis/user/info").gsub('v1/', ''),
              session_type: zuora_client.class == ZuoraAPI::Oauth ? :bearer : :basic,
              headers: auth_headers
          )
          session["ZuoraCurrentUserInfo"] = client_describe
        end
      end
      #Find matching app instances.
      if zuora_instance_id.present?
        appinstances = ZuoraConnect::AppInstance.where("zuora_entity_ids ?& array[:entities] = true AND zuora_domain = :host AND id = :id", entities: [zuora_entity_id], host: zuora_client.rest_domain, id: zuora_instance_id.to_i).pluck(:id, :name)
      else
        #if app_instance_ids is present then permissions still controlled by connect
        if params[:app_instance_ids].present?
          navbar, response = zuora_client.rest_call(url: zuora_client.rest_endpoint("navigation"))
          urls = navbar['menus'].map {|x| x['url']}
          app_env = ENV["DEIS_APP"] || "xyz123"
          url = urls.compact.select {|url| File.basename(url).start_with?(app_env + '?')}.first
          if url.blank?
            if navbar['menus'].map {|x| x['label']}.include?('Link Connect Account')
              render "zuora_connect/static/error_handled", :locals => {
                :title => "Link Account",
                :message => "Link Connect account to gain access to application."
              }, :layout => false
              return
            end
            raise ZuoraConnect::Exceptions::APIError.new(message: "#{app_env} navbar url was blank", response: response)
          else
            query_params = CGI.parse(URI.parse(url).query)
            app_instance_ids = query_params["app_instance_ids"][0]
            if app_instance_ids.present?
              begin
                task_ids = JSON.parse(Base64.urlsafe_decode64(app_instance_ids))
                appinstances = ZuoraConnect::AppInstance.where(:id => task_ids).pluck(:id, :name)
              rescue => ex
                raise ZuoraConnect::Exceptions::APIError.new(message: "Failure in parsing the navbar urls.", response: response)
              end
            end
          end
        end
        appinstances ||= ZuoraConnect::AppInstance.where("zuora_entity_ids ?& array[:entities] = true AND zuora_domain = :host", entities: [zuora_entity_id], host: zuora_client.rest_domain).pluck(:id, :name)
      end
      zuora_user_id = cookies['Zuora-User-Id'] || session["ZuoraCurrentIdentity"]['userId']
      if appinstances.size == 1
        ZuoraConnect.logger.debug("Instance is #{appinstances.to_h.keys.first}")
        @appinstance = ZuoraConnect::AppInstance.find(appinstances.to_h.keys.first)
      end
      # One deployed instance with credentials
      if defined?(@appinstance) && !@appinstance['zuora_logins'].nil?
        @zuora_user = ZuoraConnect::ZuoraUser.update_id_response(
          zuora_user_id, zuora_entity_id, session["ZuoraCurrentIdentity"],
          @appinstance
        )
        @zuora_user.session = session
        ZuoraConnect::ZuoraUser.current_user_id = zuora_user_id
        session["#{@appinstance.id}::user::localUserId"] = @zuora_user.id
        session["#{@appinstance.id}::user::email"] = session['ZuoraCurrentIdentity']["username"]
        session["#{@appinstance.id}::user::timezone"] = session['ZuoraCurrentIdentity']["timeZone"]
        session["#{@appinstance.id}::user::language"] = session['ZuoraCurrentIdentity']["language"]
        session["#{@appinstance.id}::user::locale"] = session['ZuoraCurrentIdentity']["locale"]
        session["appInstance"] = @appinstance.id
      #We have multiple, user must pick
      elsif appinstances.size > 1
        ZuoraConnect.logger.debug("User must select instance. #{@names}")
        render "zuora_connect/static/launch", :locals => {:names => appinstances.to_h}, :layout => false
        return
      #We have no deployed instance for this tenant
      else
        #Ensure user can access oauth creation API
        if !session["ZuoraCurrentUserInfo"]['permissions'].include?("permission.userManagement")
          Thread.current[:appinstance] = nil
          session["appInstance"] = nil
          render "zuora_connect/static/error_handled", :locals => {
            :title => "Application can only complete its initial setup via platform administrator",
            :message => "Please contact admin who has user managment permissions in tenant and have them click and finish setup."
          }, :layout => false
          return
        end
        Apartment::Tenant.switch!("public")
        retry_count = 3
        task_data = {}
        begin
          ActiveRecord::Base.transaction do
            ActiveRecord::Base.connection.execute('LOCK public.zuora_users IN ACCESS EXCLUSIVE MODE')
            unless defined?(@appinstance)
              appinstances = ZuoraConnect::AppInstance.where("zuora_entity_ids ?& array[:entities] = true AND zuora_domain = :host", entities: [zuora_entity_id], host: zuora_client.rest_domain).pluck(:id, :name)
              if appinstances.size > 0
                redirect_to "https://#{zuora_host}/apps/newlogin.do?retURL=#{request.fullpath}"
                return
              end
            end
            next_id = defined?(@appinstance) ? @appinstance.id : next_instance_id
            if task_data.blank?
              user = (ENV['DEIS_APP'] || "Application").split('-').map(&:capitalize).join(' ')
              body = {
                'userId' => zuora_user_id,
                'entityIds' => [zuora_entity_id.unpack("a8a4a4a4a12").join('-')],
                'customAuthorities' => [],
                'additionalInformation' => {
                  'description' => "This user is for #{user} application.",
                  'name' => "#{user} API User #{next_id}"
                }
              }
              oauth_response, response = zuora_client.rest_call(
                method: :post,
                body: body.to_json,
                url: zuora_client.rest_endpoint("genesis/clients").gsub('v1/', ''),
                session_type: zuora_client.class == ZuoraAPI::Oauth ? :bearer : :basic,
                headers: auth_headers
              )
              new_zuora_client = ZuoraAPI::Oauth.new(url: "https://#{zuora_host}", oauth_client_id: oauth_response["clientId"], oauth_secret: oauth_response["clientSecret"] )
              if session["ZuoraCurrentUserInfo"].blank?
                client_describe, response = new_zuora_client.rest_call(url: zuora_client.rest_endpoint("genesis/user/info").gsub('v1/', ''), session_type: :bearer)
              else
                client_describe = session["ZuoraCurrentUserInfo"]
              end
              available_entities = client_describe["accessibleEntities"].select {|entity| entity['id'] == zuora_entity_id}
              task_data = {
                "id": next_id,
                "name": client_describe["tenantName"],
                "mode": "Collections",
                "status": "Running",
                ZuoraConnect::AppInstance::LOGIN_TENANT_DESTINATION => {
                  "tenant_type": "Zuora",
                  "username": session["ZuoraCurrentIdentity"]["username"],
                  "url": new_zuora_client.url,
                  "status": "Active",
                  "oauth_client_id": oauth_response['clientId'],
                  "oauth_secret": oauth_response['clientSecret'],
                  "authentication_type": "OAUTH",
                  "entities": available_entities.map {|e| e.merge({'displayName' => client_describe["tenantName"]})}
                },
                "tenant_ids": available_entities.map{|e| e['entityId']}.uniq,
              }
            end
            if defined?(@appinstance)
              @appinstance.zuora_logins = task_data
              @appinstance.save(:validate => false)
            else
              @appinstance = new_instance(
                next_id,
                zuora_entity_id,
                zuora_client.rest_domain,
                task_data: task_data,
                retry_count: retry_count
              )
            end
          end
        rescue ActiveRecord::RecordNotUnique
          retry if (retry_count -= 1).positive?
          return
        end
        Apartment::Tenant.switch!("public")
        begin
          Apartment::Tenant.create(@appinstance.id.to_s)
        rescue Apartment::TenantExists => ex
          ZuoraConnect.logger.debug("Tenant Already Exists")
        end
        @appinstance.refresh
        session["appInstance"] = @appinstance.id
      end
    rescue ZuoraAPI::Exceptions::ZuoraAPIAuthenticationTypeError => ex
      output_xml, input_xml, response = zuora_client.soap_call(errors: [], z_session: false) do |xml|
        xml['api'].getUserInfo
      end
      final_error = output_xml.xpath('//fns:FaultCode', 'fns' =>'http://fault.api.zuora.com/').text
      session.clear
      if final_error.blank?
        ZuoraConnect.logger.warn("UI Authorization Error", ex, zuora: zuora_details.merge({:error => response.body}))
      elsif final_error != "INVALID_SESSION"
        ZuoraConnect.logger.warn("UI Authorization Error", ex, zuora: zuora_details.merge({:error => final_error}))
      else
        ZuoraConnect.logger.info("UI Authorization Error", ex, zuora: zuora_details.merge({:error => final_error}))
      end
      redirect_to "https://#{zuora_host}/apps/newlogin.do?retURL=#{request.fullpath}"
      return
    rescue ZuoraAPI::Exceptions::ZuoraAPIError, Exception => ex
      if ex.message.include?("Referenced User resource(s) not found") && ex.class == ZuoraAPI::Exceptions::ZuoraAPIError
        locals = {title: "Provisioning Error", message: "New tenants need to be provisioned by API Gateway('#{ex.message}'). Please contact support."}
        render "zuora_connect/static/error_handled", locals: locals, status: 200, layout: false
      else
        session.clear
        if defined?(ex.response) && ex.response.present? && defined?(ex.response.body)
          zuora_details.merge!({:error => ex.response.body})
        end
        ZuoraConnect.logger.error("UI Authorization Error", ex, zuora: zuora_details)
        render "zuora_connect/static/error_unhandled", locals: {exception: ex, skip_exception: true}, layout: false, status: 500
      end
      return
    end
  elsif request["data"].present? && (request["connectInstanceId"].present? || /^([A-Za-z0-9+\/\-\_]{4})*([A-Za-z0-9+\/]{4}|[A-Za-z0-9+-_\/]{3}=|[A-Za-z0-9+\/]{2}==)$/.match(request["data"].to_s))
    session.clear
    values = JSON.parse(ZuoraConnect::AppInstance.decrypt_response(Base64.urlsafe_decode64(request["data"])))
    values.fetch("param_data", {}).each do |k ,v|
      params[k] = v
    end
    session["#{values["appInstance"]}::destroy"] = values["destroy"]
    session["appInstance"] = values["appInstance"]
    if values["current_user"]
      session["#{values["appInstance"]}::admin"] = values["current_user"]["admin"] ? values["current_user"]["admin"] : false
      session["#{values["appInstance"]}::user::timezone"] = values["current_user"]["timezone"]
      session["#{values["appInstance"]}::user::locale"] = values["current_user"]["locale"]
      session["#{values["appInstance"]}::user::email"] = values["current_user"]["email"]
    end
    @appinstance = ZuoraConnect::AppInstance.find_by(:id => values["appInstance"].to_i)
    if @appinstance.blank?
      Apartment::Tenant.switch!("public")
      begin
        Apartment::Tenant.create(values["appInstance"].to_s)
      rescue Apartment::TenantExists => ex
        ZuoraConnect.logger.debug("Tenant Already Exists")
      end
      mapped_values = {:api_token => values['api_token'], :token => values['api_token'], :access_token => values["access_token"], :refresh_token => values["refresh_token"], :oauth_expires_at => values["expires"]}
      @appinstance = ZuoraConnect::AppInstance.new(mapped_values.merge({:id => values["appInstance"].to_i}))
      @appinstance.save(:validate => false)
    else
      mapped_values = {:access_token => values["access_token"], :refresh_token => values["refresh_token"], :oauth_expires_at => values["expires"]}
      @appinstance.assign_attributes(mapped_values)
      if @appinstance.access_token_changed? && @appinstance.refresh_token_changed?
        @appinstance.save(:validate => false)
      else
        raise ZuoraConnect::Exceptions::AccessDenied.new("Authorization mismatch. Possible tampering with session.")
      end
    end
  else
    if session["appInstance"].present?
      @appinstance = ZuoraConnect::AppInstance.find_by(:id => session["appInstance"])
    else
      raise ZuoraConnect::Exceptions::AccessDenied.new("No application state or session found.")
    end
  end
end

def zuora_user

def zuora_user
  return @zuora_user
end