require "net/http"
require "json"
require "todoist/config"
require 'net/http/post/multipart'
require 'mimemagic'
require 'openssl'

module Todoist
  module Util
    class NetworkHelper

      

      def initialize(client)
        @client = client
        @command_cache = Concurrent::Array.new([])
        @command_mutex = Mutex.new
        @temp_id_callback_cache = Concurrent::Array.new([])
        @last_request_time = 0.0
        
      end
      
      def configure_http(command)
        http = Net::HTTP.new(Config.getURI()[command].host, Config.getURI()[command].port)
        http.use_ssl = true
        http.verify_mode = OpenSSL::SSL::VERIFY_NONE
        return http
      end

      def configure_request(command, params)
        request = Net::HTTP::Post.new(Config.getURI()[command].request_uri)
        request.set_form_data(params)
        return request
      end
      
      # Files need to be of class UploadIO
      
      def get_multipart_response(command, params={})
        token = {token: @client.token}
        http = configure_http(command)
        url = Config.getURI()[command]
        http.start do
          req = Net::HTTP::Post::Multipart.new(url, token.merge(params))
          response = http.request(req)
          begin
            return JSON.parse(response.body)
          rescue JSON::ParserError
              return response.body
          end
        end
      end
    
      def get_response(command, params ={}, token = true)
        token = token ? {token: @client.token} : {}
        http = configure_http(command)
        request = configure_request(command, token.merge(params))
        retry_after_secs = Todoist::Config.retry_time
        # Hack to fix encoding issues with Net:HTTP for login case
        request.body = request.body.gsub '%40', '@' unless token
        while true
          response = throttle_request(http, request)
          case response.code.to_i
          when 200 
            begin
              return JSON.parse(response.body)
            rescue JSON::ParserError
              return response.body
            end
          when 400
            raise StandardError, "HTTP 400 Error - The request was incorrect."
          when 401
            raise StandardError, "HTTP 401 Error - Authentication is required, and has failed, or has not yet been provided."
          when 403
            raise StandardError, "HTTP 403 Error - The request was valid, but for something that is forbidden."
          when 404
            raise StandardError, "HTTP 404 Error - The requested resource could not be found."
          when 429
            puts("Encountered 429 - retry after #{retry_after_secs}")
            sleep(retry_after_secs)
            retry_after_secs *= Todoist::Config.retry_time
          when 500
            raise StandardError, "HTTP 500 Error - The request failed due to a server error."
          when 503
            raise StandardError, "HTTP 503 Error - The server is currently unable to handle the request."
          end
        end
        
      end
      
      def throttle_request(http, request)
        time_since_last_request = Time.now.to_f - @last_request_time
        
        if (time_since_last_request < Todoist::Config.delay_between_requests)
          wait = Todoist::Config.delay_between_requests - time_since_last_request
          puts("Throttling request by: #{wait}")
          sleep(wait)
        end
        @last_request_time = Time.now.to_f
        http.request(request)
      end
      
      # Prepares a file for multipart upload
      def multipart_file(file)
          filename = File.basename(file)
          mime_type = MimeMagic.by_path(filename).type
          return UploadIO.new(file, mime_type, filename)
      end
        
      def queue(command, callback = nil) 
        @command_mutex.synchronize do
          @command_cache.push(command)
          @temp_id_callback_cache.push(callback) if callback
        end
        
      end
      
      def sync
        @command_mutex.synchronize do    
          response = get_sync_response({commands: @command_cache.to_json})
          @command_cache.clear
          # Process callbacks here
          temp_id_mappings = response["temp_id_mapping"]
          @temp_id_callback_cache.each do |callback| 
              callback.(temp_id_mappings)
          end
          @temp_id_callback_cache.clear
        end
      end

      def get_sync_response(params)
        get_response(Config::TODOIST_SYNC_COMMAND, params)
      end

    end
  end
end