# frozen_string_literal: true

RSpec.configure do |config|
  config.around(:each, :mock_auth => lambda { |v| !!v }) do |example|
    options                               = example.metadata[:mock_auth]

    authentication_type                   = if options.is_a?(Hash) && options[:type]
                                              options[:type]
                                            else
                                              :json_web_token
                                            end

    klass                                 = case options
                                            when TrueClass
                                              User
                                            when Hash
                                              options[:class] || described_class
                                                                   .name[/(.*?)::/, 1]
                                                                   .concat('::User')
                                                                   .constantize
                                            else
                                              options
                                            end

    underscored_class_name                = klass
                                              .name[/(?:.*::)?(\w+)\z/, 1]
                                              .gsub(/([a-z])([A-Z])/, '\1_\2')
                                              .downcase

    current_class_method                  = if options.is_a?(Hash) && options[:method]
                                              options[:method] || :current_user
                                            else
                                              :"current_#{underscored_class_name}"
                                            end

    class_instance_overrides              = if options.is_a?(Hash) && options[:class_instance_overrides]
                                              options[:class_instance_overrides]
                                            else
                                              {}
                                            end

    class_instance_traits                 = if options.is_a?(Hash) && options[:class_instance_traits]
                                              options[:class_instance_traits]
                                            else
                                              {}
                                            end

    instance                              = if options.is_a?(Hash) && options[:strategy] == :instance
                                              klass.new(class_instance_overrides)
                                            else
                                              FactoryBot.create(underscored_class_name.to_sym, *class_instance_traits, class_instance_overrides)
                                            end

    inferred_auth_method                  = if options.is_a?(Hash) && options[:authentication_method]
                                              options[:authentication_method]
                                            else
                                              :"authenticate_#{underscored_class_name}!"
                                            end

    authentication_controller_class       = if example.metadata[:type] == :controller
                                              described_class
                                            else
                                              ApplicationController
                                            end

    authentication_controller_instance    = authentication_controller_class.new

    authentication_successful             = if options.is_a?(Hash) && options.has_key?(:status)
                                              options[:status] == :authorized
                                            else
                                              true
                                            end

    authentication_result                 = authentication_successful ? instance : nil

    if authentication_type == :standard
      authentication_method = if authentication_controller_instance.respond_to?(inferred_auth_method, true)
                                inferred_auth_method
                              elsif authentication_controller_instance.respond_to?(:authenticate, true)
                                :authenticate
                              end

      authentication_controller_class.__send__(:define_method, authentication_method) { authentication_successful }
      authentication_controller_class.__send__(:define_method, current_class_method)  { authentication_result }
      authentication_controller_class.__send__(:helper_method, current_class_method)
      example.example_group_instance.class.let(current_class_method)                  { authentication_result }

      example.run

      authentication_controller_class.__send__(:remove_method, current_class_method)
    elsif authentication_type == :json_web_token
      @token_data = if options.is_a?(Hash) && options[:data]
                      options[:data]
                    else
                      [
                        {
                          'aid' => options[:audience_id] || instance['account_id'] || instance['id'],
                          'aud' => options[:audience] || instance.class.name,
                          'exp' => options[:expired_at] || 1.day.from_now.utc.to_i,
                          'iat' => Time.now.utc.to_i,
                          'iss' => options[:issuer] || 'rspeckled',
                          'jti' => SecureRandom.uuid,
                          'nbf' => 1.day.ago.utc.to_i,
                          'rol' => options[:roles] || 'standard',
                          'sid' => options[:subject_id],
                          'sub' => options[:subject],
                        },
                        {
                          'typ' => 'JWT',
                          'cty' => 'application/json-web-token',
                        },
                      ]
                    end

      example.example_group_instance.class.let(current_class_method) { authentication_result }

      example.run
    else
      fail ArgumentError, 'You must specify a valid type for the :mock_auth metadata'
    end

    instance.delete unless options.is_a?(Hash) && options[:strategy] == :instance
  end

  config.before(:each, :mock_auth => lambda { |v| !!v }) do |_example|
    request.env['X_JSON_WEB_TOKEN'] = @token_data
  end
end