# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/

module ApplicationController::Authenticates
  extend ActiveSupport::Concern

  private

  def authentication_check(basic_auth_prompt: nil)
    user = authentication_check_only

    # check if basic_auth fallback is possible
    if basic_auth_prompt && !user
      request_http_basic_authentication
      return false
    end

    # return auth not ok
    if !user
      raise Exceptions::Forbidden, __('Authentication required')
    end

    # return auth ok
    true
  end

  def authentication_check_only
    if %w[test development].include?(Rails.env) && ENV['FAKE_SELENIUM_LOGIN_USER_ID'].present? && session[:user_id].blank?
      session[:user_id] = ENV['FAKE_SELENIUM_LOGIN_USER_ID'].to_i
      session[:user_device_updated_at] = Time.zone.now
      session[:authentication_type] = 'password'
    end

    # logger.debug 'authentication_check'
    # logger.debug params.inspect
    # logger.debug session.inspect
    # logger.debug cookies.inspect
    authentication_errors = []

    # already logged in, early exit
    if session.id && session[:user_id]
      logger.debug { 'session based auth check' }
      user = User.lookup(id: session[:user_id])
      return authentication_check_prerequesits(user, 'session') if user

      authentication_errors.push("Can't find User with ID #{session[:user_id]} from Session")
    end

    # check http basic based authentication
    authenticate_with_http_basic do |username, password|
      request.session_options[:skip] = true # do not send a session cookie
      logger.debug { "http basic auth check '#{username}'" }
      if Setting.get('api_password_access') == false
        raise Exceptions::Forbidden, 'API password access disabled!'
      end

      auth = Auth.new(username, password)

      begin
        auth.valid!
        return authentication_check_prerequesits(auth.user, 'basic_auth')
      rescue Auth::Error::AuthenticationFailed
        authentication_errors.push(__('Invalid BasicAuth credentials'))
      rescue Auth::Error::TwoFactorRequired
        authentication_errors.push(__('Two-factor authentication is not supported with HTTP BasicAuth.'))
      end
    end

    # check http token based authentication
    authenticate_with_http_token do |token_string, _options|
      logger.debug { "http token auth check '#{token_string}'" }
      request.session_options[:skip] = true # do not send a session cookie
      if Setting.get('api_token_access') == false
        raise Exceptions::Forbidden, 'API token access disabled!'
      end

      user = Token.check(
        action:        'api',
        token:         token_string,
        inactive_user: true,
      )

      if user
        token = Token.find_by(token: token_string)

        token.last_used_at = Time.zone.now
        token.save!

        if token.expires_at &&
           Time.zone.today >= token.expires_at
          raise Exceptions::NotAuthorized, __('Not authorized (token expired)!')
        end

        @_token = token # remember for Pundit authorization / permit!
      end

      @_token_auth = token_string # remember for permission_check
      return authentication_check_prerequesits(user, 'token_auth') if user

      authentication_errors.push(__("Can't find User for Token"))
    end

    # check oauth2 token based authentication
    token = Doorkeeper::OAuth::Token.from_bearer_authorization(request)
    if token
      request.session_options[:skip] = true # do not send a session cookie
      logger.debug { "OAuth2 token auth check '#{token}'" }
      access_token = Doorkeeper::AccessToken.by_token(token)

      raise Exceptions::NotAuthorized, __('The provided token is invalid.') if !access_token

      # check expire
      if access_token.expires_in && (access_token.created_at + access_token.expires_in) < Time.zone.now
        raise Exceptions::NotAuthorized, __('OAuth2 token is expired!')
      end

      # if access_token.scopes.empty?
      #   raise Exceptions::NotAuthorized, 'OAuth2 scope missing for token!'
      # end

      user = User.find(access_token.resource_owner_id)
      return authentication_check_prerequesits(user, 'token_auth') if user

      authentication_errors.push("Can't find User with ID #{access_token.resource_owner_id} for OAuth2 token")
    end

    return false if authentication_errors.blank?

    raise Exceptions::NotAuthorized, authentication_errors.join(', ')
  end

  def authentication_check_prerequesits(user, auth_type)
    raise Exceptions::Forbidden, __('Maintenance mode enabled!') if in_maintenance_mode?(user)

    raise Exceptions::NotAuthorized, Auth::Error::AuthenticationFailed::MESSAGE if !user.active

    current_user_set(user, auth_type)
    user_device_log(user, auth_type)
    logger.debug { "#{auth_type} for '#{user.login}'" }
    user
  end

  def authenticate_and_authorize!
    authentication_check && authorize!
  end
end