module ThreeScale
  module OAuth2
    class ClientBase
      class ClientError < StandardError
        include Bugsnag::MetaData
      end

      class CallbackUrl
        def self.call(base_url, sso_name, query_options)
          url = base_url.dup
          url << '/auth' if base_url.exclude?('/auth')
          url << "/#{sso_name}/callback"
          url << "?#{query_options.to_query}" if query_options.present?
          url
        end
      end

      # @return [ThreeScale::OAuth2::Client::Authentication]
      attr_reader :authentication

      # @return [ThreeScale::OAuth2::Client::ClientBase]
      attr_reader :client

      attr_reader :raw_info

      delegate :client_id, to: :authentication

      # @param [ThreeScale::OAuth2::Client::Authentication] authentication
      def initialize(authentication)
        @authentication = authentication
        @client = ::OAuth2::Client.new(@authentication.client_id, @authentication.client_secret, options)
        @raw_info = {}
      end

      # @param [String] code
      # @param [ActionDispatch::Request] request
      def authenticate!(code, request)
        options = authenticate_options(request)
        Rails.logger.debug("[OAuth2] Requesting Access Token for Code #{code} with options: #{options}")
        @access_token = client.auth_code.get_token(code, options)
        Rails.logger.debug("[OAuth2] Got Access Token #{@access_token.token} For Code #{code}")
        fetch_info
        user_data
      rescue ::OAuth2::Error => error
        Rails.logger.error("[OAuth2] Failed to get Access Token for Code #{code} with: #{error}")
        {
            error: 'The code is incorrect or expired.'
        }
      end

      def user_data
        {
            email: email,
            email_confirmed: email_confirmed?,
            username: username,
            uid: uid,
            org_name: org_name,
            kind: kind,
            authentication_id: authentication_id
        }
      end

      def access_token
        @access_token or raise 'Missing access_token, authenticate first'
      end

      def fetch_info
        @raw_info = access_token.get(user_info_url).parsed.presence || @raw_info
      end

      def authorize_url(base_url, query_options = {})
        client.auth_code.authorize_url(redirect_uri: callback_url(base_url, query_options), scope: scopes)
      end

      def callback_url(base_url, query_options = {})
        CallbackUrl.call(base_url, authentication.system_name, query_options)
      end

      def email
        nil
      end

      def email_confirmed?
        false
      end

      def username
        nil
      end

      def org_name
        nil
      end

      def uid
        raw_info[authentication.identifier_key]
      end

      def authentication_id
        uid
      end

      def kind
        'base'
      end

      def authenticate_options(request)
        {}
      end

      private

      def scopes
        nil
      end

      def user_info_url
        authentication.user_info_url
      end

      def options
        authentication.options.to_hash
      end
    end
  end
end
