# frozen_string_literal: true

module OmniAuth
  module Identity
    # This module provides an include-able interface for implementing the
    # necessary API for OmniAuth Identity to properly locate identities
    # and provide all necessary information.
    #
    # All methods marked as abstract must be implemented in the
    # including class for things to work properly.
    #
    ### Singleton API
    #
    # * locate(key)
    # * create(*args) - Deprecated in v3.0.5; Will be removed in v4.0
    #
    ### Instance API
    #
    # * save
    # * persisted?
    # * authenticate(password)
    #
    module Model
      SCHEMA_ATTRIBUTES = %w[name email nickname first_name last_name location description image phone].freeze

      class << self
        def included(base)
          base.extend(ClassMethods)
          base.extend(ClassCreateApi) unless base.respond_to?(:create)
          i_methods = base.instance_methods
          base.include(InstanceSaveApi) unless i_methods.include?(:save)
          base.include(InstancePersistedApi) unless i_methods.include?(:persisted?)
        end
      end

      module ClassMethods
        # Authenticate a user with the given key and password.
        #
        # @param [String] conditions The unique login key provided for a given identity.
        # @param [String] password The presumed password for the identity.
        # @return [Model, false] An instance of the identity model class.
        def authenticate(conditions, password)
          instance = locate(conditions)
          return false unless instance

          instance.authenticate(password)
        end

        # Used to set or retrieve the method that will be used to get
        # and set the user-supplied authentication key.
        # @return [String] The method name.
        def auth_key(method = false)
          @auth_key = method.to_s unless method == false
          @auth_key = nil if !defined?(@auth_key) || @auth_key == ""

          @auth_key || "email"
        end

        # Locate an identity given its unique login key.
        #
        # @abstract
        # @param [String] _key The unique login key.
        # @return [Model] An instance of the identity model class.
        def locate(_key)
          raise NotImplementedError
        end
      end

      module ClassCreateApi
        # Persists a new Identity object to the ORM.
        # Only included if the class doesn't define create, as a reminder to define create.
        # Override as needed per ORM.
        #
        # @deprecated v4.0 will begin using {#new} with {#save} instead.
        # @abstract
        # @param [Hash] _args Attributes of the new instance.
        # @return [Model] An instance of the identity model class.
        # @since 3.0.5
        def create(*_args)
          raise NotImplementedError
        end
      end

      module InstanceSaveApi
        # Persists a new Identity object to the ORM.
        # Default raises an error.  Override as needed per ORM.
        # This base version's arguments are modeled after ActiveModel
        #   since it is a pattern many ORMs follow
        #
        # @abstract
        # @return [Model] An instance of the identity model class.
        # @since 3.0.5
        def save(**_options, &_block)
          raise NotImplementedError
        end
      end

      module InstancePersistedApi
        # Checks if the Identity object is persisted in the ORM.
        # Default raises an error.  Override as needed per ORM.
        #
        # @abstract
        # @return [true or false] true if object exists, false if not.
        # @since 3.0.5
        def persisted?
          raise NotImplementedError
        end
      end

      # Returns self if the provided password is correct, false
      # otherwise.
      #
      # @abstract
      # @param [String] _password The password to check.
      # @return [self or false] Self if authenticated, false if not.
      def authenticate(_password)
        raise NotImplementedError
      end

      # An identifying string that must be globally unique to the
      # application. Defaults to stringifying the `id` method.
      #
      # @return [String] An identifier string unique to this identity.
      def uid
        if respond_to?(:id)
          return if id.nil?

          id.to_s
        else
          raise NotImplementedError
        end
      end

      # Used to retrieve the user-supplied authentication key (e.g. a
      # username or email). Determined using the class method of the same name,
      # defaults to `:email`.
      #
      # @return [String] An identifying string that will be entered by
      #   users upon sign in.
      def auth_key
        if respond_to?(self.class.auth_key.to_sym)
          send(self.class.auth_key)
        else
          raise NotImplementedError
        end
      end

      # Used to set the user-supplied authentication key (e.g. a
      # username or email. Determined using the `.auth_key` class
      # method.
      #
      # @param [String] value The value to which the auth key should be
      #   set.
      def auth_key=(value)
        auth_key_setter = "#{self.class.auth_key}=".to_sym
        if respond_to?(auth_key_setter)
          send(auth_key_setter, value)
        else
          raise NotImplementedError
        end
      end

      # A hash of as much of the standard OmniAuth schema as is stored
      # in this particular model. By default, this will call instance
      # methods for each of the attributes it needs in turn, ignoring
      # any for which `#respond_to?` is `false`.
      #
      # If `first_name`, `nickname`, and/or `last_name` is provided but
      # `name` is not, it will be automatically calculated.
      #
      # @return [Hash] A string-keyed hash of user information.
      def info
        info = {}
        SCHEMA_ATTRIBUTES.each_with_object(info) do |attribute, hash|
          hash[attribute] = send(attribute) if respond_to?(attribute)
        end
        info["name"] ||= [info["first_name"], info["last_name"]].join(" ").strip if info["first_name"] || info["last_name"]
        info["name"] ||= info["nickname"]
        info
      end
    end
  end
end
