require 'devise/multi_email/parent_model_extensions'

module Devise
  module Models
    module EmailAuthenticatable
      def devise_scope
        self.class.multi_email_association.model_class
      end
    end

    module MultiEmailAuthenticatable
      extend ActiveSupport::Concern

      included do
        include Devise::MultiEmail::ParentModelExtensions

        attr_accessor :current_login_email

        devise :database_authenticatable

        include AuthenticatableExtensions
      end

      def self.required_fields(klass)
        []
      end

      module AuthenticatableExtensions
        extend ActiveSupport::Concern

        included do
          multi_email_association.configure_autosave!{ include AuthenticatableAutosaveExtensions }
          multi_email_association.include_module(EmailAuthenticatable)
        end

        delegate :skip_confirmation!, to: :primary_candidate_email_record, allow_nil: false

        # Gets the primary email address of the user.
        def email
          effective_email_record.try(:email)
        end

        def effective_email_record
          primary = multi_email.primary_email_record
          return primary if primary&.confirmed?
          multi_email.confirmed_emails.first || primary || multi_email.primary_candidate_email_record
        end

        # Sets the default email address of the user.
        def email=(new_email)
          multi_email.change_primary_email_to(new_email, allow_unconfirmed: true)
        end
      end

      module AuthenticatableAutosaveExtensions
        extend ActiveSupport::Concern

        included do
          # Toggle `primary` value for all emails if `autosave` is not on
          after_save do
            multi_email.filtered_emails.each do |email|
              # update value in database without persisting any other changes
              email.save if email.changes.key?(:primary)
            end
          end
        end
      end

      module ClassMethods
        def find_first_by_auth_conditions(tainted_conditions, opts = {})
          filtered_conditions = devise_parameter_filter.filter(tainted_conditions.dup)
          criteria = filtered_conditions.extract!(:email, :unconfirmed_email, :confirmation_token)

          if criteria.keys.any?
            conditions = filtered_conditions.to_h.merge(opts).
              reverse_merge(build_conditions(criteria))

            resource = joins(multi_email_association.name).find_by(conditions)
            resource.current_login_email = criteria.values.first if resource
            resource
          else
            super(tainted_conditions, opts)
          end
        end

        def find_by_email(email)
          joins(multi_email_association.name).where(build_conditions email: email).first
        end

        def build_conditions(criteria)
          criteria = devise_parameter_filter.filter(criteria)
          # match the primary email record if the `unconfirmed_email` column is specified
          if Devise::MultiEmail.only_login_with_primary_email? || criteria[:unconfirmed_email]
            criteria.merge!(primary: true)
          end

          { multi_email_association.reflection.table_name.to_sym => criteria }
        end
      end
    end
  end
end
