# frozen_string_literal: true

module RailsBase::Mfa::Sms
  class Validate < RailsBase::ServiceBase
    delegate :params, to: :context
    delegate :session_mfa_user_id, to: :context
    delegate :current_user, to: :context
    delegate :input_reason, to: :context
    delegate :sms_code, to: :context
    delegate :mfa_event, to: :context

    def call
      if sms_code.present?
        log(level: :info, msg: "Raw sms code was passed in")
        mfa_code = sms_code
      else
        log(level: :info, msg: "Code Array was passed in. Manipulating Data first")
        array = convert_to_array
        if array.length != RailsBase::Authentication::Constants::MFA_LENGTH
          log(level: :warn, msg: "Not enough params for MFA code. Given #{array}. Expected of length #{RailsBase::Authentication::Constants::MFA_LENGTH}")
          context.fail!(message: RailsBase::Authentication::Constants::MV_FISHY, redirect_url: RailsBase.url_routes.new_user_session_path, level: :alert)
        end

        mfa_code = array.join
      end


      log(level: :info, msg: "mfa code received: #{mfa_code}")
      datum = get_short_lived_datum(mfa_code)
      log(level: :info, msg: "Datum returned with: #{datum}")

      validate_datum?(datum)
      validate_user_consistency?(datum)
      validate_current_user?(datum) if current_user

      context.user = datum[:user]
    end

    def validate_current_user?(datum)
      return true if current_user.id == datum[:user].id

      # User MFA for a different user matched the session token
      # However, those did not match the current user signed in
      # Something is very 🐟
      log(level: :error, msg: "Someone is a teapot. Current logged in user does not equal mfa code.")
      context.fail!(message: 'You are a teapot', redirect_url: RailsBase.url_routes.signout_path, level: :warn)
    end

    def validate_datum?(datum)
      return true if datum[:valid]

      if datum[:found]
        # MFA is either expired or the incorrect reason. Either way it does not match
        msg = "Errors with MFA: #{datum[:invalid_reason].join(", ")}. Please login again"
        log(level: :warn, msg: msg)
        context.fail!(message: msg, redirect_url: RailsBase.url_routes.new_user_session_path, level: :warn)
      end

      # MFA does not exist for any reason type
      log(level: :warn, msg: "Could not find MFA code. Incorrect MFA code")

      context.fail!(message: "Incorrect SMS code.", redirect_url: RailsBase.url_routes.mfa_with_event_path(mfa_event: mfa_event.event, type: RailsBase::Mfa::SMS), level: :warn)
    end

    def validate_user_consistency?(datum)
      return true if datum[:user].id == session_mfa_user_id.to_i
      log(level: :warn, msg: "Datum user does not match session user. [#{datum[:user].id}, #{session_mfa_user_id.to_i}]")

      # MFA session token user does not match the datum user
      # Something is very 🐟
      context.fail!(message: RailsBase::Authentication::Constants::MV_FISHY, redirect_url: RailsBase.url_routes.new_user_session_path, level: :alert)
    end

    def get_short_lived_datum(mfa_code)
      log(level: :debug, msg: "Looking for #{mfa_code} with reason #{reason}")
      ShortLivedData.find_datum(data: mfa_code, reason: reason)
    end

    def convert_to_array
      array = []
      return array unless params.dig(:mfa).respond_to? :keys

      RailsBase::Authentication::Constants::MFA_LENGTH.times do |index|
        var_name = "#{RailsBase::Authentication::Constants::MV_BASE_NAME}#{index}".to_sym
        array << params[:mfa][var_name]
      end

      array.compact
    end

    def reason
      input_reason || RailsBase::Authentication::Constants::MFA_REASON
    end

    def validate!
      if sms_code.nil?
        raise 'params is not present' if params.nil?
      end

      raise 'mfa_event is expected to be a RailsBase::MfaEvent' unless RailsBase::MfaEvent === mfa_event

      raise 'session_mfa_user_id is not present' if session_mfa_user_id.nil?
    end
  end
end