require "singleton"

module Evertils
  module Common
    class Authentication < Common::Generic
      include Singleton

      def initialize
        begin
          # attempt to login as the Evernote user
          prepare_user

          # quit with message if requirements not met
          Notify.error("Evernote developer token is not configured properly!\n$EVERTILS_TOKEN == nil") if Evertils.token.nil?
          Notify.error("Evernote API requires an update.  Latest version is #{@version}") if requires_update

          # prepare the main data model access point
          prepare_note_store
        rescue Evernote::EDAM::Error::EDAMUserException => e
          handle_edam_errors(e)
        rescue Evernote::EDAM::Error::EDAMSystemException => e
          handle_edam_errors(e)
        end
      end

      def info
        {
          user: {
            name: @user.name,
            username: @user.username,
            id: @user.id
          },
          shard: @user.shardId,
          api_version: @version,
        }
      end

      def call(func, *args)
        begin
          if args.size > 0
            @noteStore.method(func.to_s).call(Evertils.token, *args)
          else
            @noteStore.method(func.to_s).call(Evertils.token)
          end
        rescue Evernote::EDAM::Error::EDAMUserException => e
          handle_edam_errors(e)
        rescue Evernote::EDAM::Error::EDAMSystemException => e
          handle_edam_errors(e)
        end
      end

      def call_user(func, *args)
        begin
          if args.size > 0
            @userStore.method(func.to_s).call(*args)
          else
            @userStore.method(func.to_s).call
          end
        rescue Evernote::EDAM::Error::EDAMUserException => e
          handle_edam_errors(e)
        rescue Evernote::EDAM::Error::EDAMSystemException => e
          handle_edam_errors(e)
        end
      end

      private

      def prepare_user
        userStoreUrl = "https://#{Evertils.host}/edam/user"

        userStoreTransport = Thrift::HTTPClientTransport.new(userStoreUrl)
        userStoreProtocol = Thrift::BinaryProtocol.new(userStoreTransport)
        @userStore = ::Evernote::EDAM::UserStore::UserStore::Client.new(userStoreProtocol)
        @user = call_user(:getUser, Evertils.token)
        @major_ver = ::Evernote::EDAM::UserStore::EDAM_VERSION_MAJOR
        @minor_ver = ::Evernote::EDAM::UserStore::EDAM_VERSION_MINOR

        @version = "#{@major_ver}.#{@minor_ver}"

        if Evertils.is_test?
          Notify.spit "TEST USER: #{info[:user]}"
        end
      end

      def prepare_note_store
        noteStoreUrl = call_user(:getNoteStoreUrl, Evertils.token)

        noteStoreTransport = Thrift::HTTPClientTransport.new(noteStoreUrl)
        noteStoreProtocol = Thrift::BinaryProtocol.new(noteStoreTransport)
        @noteStore = ::Evernote::EDAM::NoteStore::NoteStore::Client.new(noteStoreProtocol)
      end

      def requires_update
        !call_user(:checkVersion, "evernote-data", @major_ver, @minor_ver)
      end

      private

      def handle_edam_errors(e)
        Notify.warning("Problem authenticating, EDAM code #{e.errorCode}")
        Notify.warning("Type: #{e.class}")

        case e.errorCode
        when 1
          message = 'An unknown error occurred'
        when 2
          message = 'The format of the request data was incorrect'
        when 3
          message = 'Not permitted to perform action'
        when 4
          message = 'Unexpected problem with the service'
        when 5
          message = 'A required parameter/field was absent'
        when 6
          message = 'Operation denied due to data model limit'
        when 7
          message = 'Operation denied due to user storage limit'
        when 8
          message = 'Username and/or password incorrect'
        when 9
          message = 'Authentication token expired'
        when 10
          message = 'Change denied due to data model conflict'
        when 11
          message = 'Content of submitted note was malformed'
        when 12
          message = 'Service shard with account data is temporarily down'
        when 13
          message = 'Operation denied due to data model limit, where something such as a string length was too short'
        when 14
          message = 'Operation denied due to data model limit, where something such as a string length was too long'
        when 15
          message = 'Operation denied due to data model limit, where there were too few of something'
        when 16
          message = 'Operation denied due to data model limit, where there were too many of something'
        when 17
          message = 'Operation denied because it is currently unsupported'
        when 18
          message = 'Operation denied because access to the corresponding object is prohibited in response to a take-down notice'
        when 19
          minutes = (e.rateLimitDuration.to_i/60).to_i
          message = "You are rate limited!  Wait #{minutes} minutes"
        end

        message += "\n- "
        message += e.parameter unless e.parameter.nil?

        Notify.warning(message)
        exit(0)
      end
    end
  end
end