require 'base64'
require 'net/http'
require 'openssl'
require 'yaml'
require 'open-uri'
require_relative 'filelogger'
require 'open3'

# class representing a Wendelin client
class WendelinClient

  HTTP_MESSAGE_401 = "Unauthorized access. Please check your user credentials and try again."
  HTTP_MESSAGE_400 = "There was a problem with the http request. If the error persists, please contact the administrator."
  HTTP_MESSAGE_5XX = "Internal Server Error: if the error persists, please contact the administrator."
  HTTP_MESSAGE_OTHER = "Sorry, an error ocurred. If the error persists, please contact the administrator."
  HTTP_MESSAGE_NOT_2XX = "HTTP-NOT-OK"
  HTTP_MESSAGE_EXCEPTION = "HTTP-ERROR"
  HTTP_MEMORY_ERROR = "MEMORY-ERROR"
  HTTP_REFERENCE_EXIST = "REFERENCE-EXIST"

  def initialize(erp5_url, user, password)
    @erp5_url = erp5_url
    @user = user
    @password = password
    @banned_references_list = []
    @logger = LogManager.instance()
    @last_ingestion = Time.new - 2
    begin
      # check credentials
      uri = URI(URI.escape("#{@erp5_url}"))
      uri = URI(URI.escape("#{uri.scheme}://#{uri.host}/erp5/getDataStreamList?data_set_reference="))
      scheme = uri.scheme == 'https' ? "https://" : "http://"
      check_credential_url = uri.to_s.sub! scheme, scheme + @user + ":" + @password + "@"
      command = "curl --request GET '" + check_credential_url + "'"
      stdout,stderr,status = Open3.capture3(command)
      @logger.info("Checking credentials...")
      print "[INFO] Checking credentials..."
      if status.success?
        if stdout.include? "'"
          stdout = stdout.gsub! "'", '"'
        end
        dict = JSON.parse(stdout)
        if dict["status_code"] == 1
          @logger.error(dict["error_message"], print=TRUE)
          @logger.abortExecution()
        end
        @logger.info("OK")
        puts "OK"
      else
        @logger.error(HTTP_MESSAGE_400, print=TRUE)
        @logger.error(stderr)
        @logger.abortExecution()
      end
    rescue Exception => e
	    @logger.error("HTTP ERROR: " + e.to_s, print=TRUE)
	    @logger.error(e.backtrace)
      @logger.abortExecution()
    end
  end

  def checkReferenceChars(reference)
    if ["&", ";", "#", "%", '"', "+"].any? { |char| reference.include?(char) }
      raise "invalid char in filename. Following chars are not allowed for filenames: \& \; \% \" \+ \# Please rename it."
    end
  end

  def exists(reference)
    checkReferenceChars(reference)
    uri = URI(URI.escape("#{@erp5_url}/ingestionReferenceExists?reference=#{reference}"))
    begin
      response = handleRequest(uri, nil, nil, non_json_content=true)
      res = response["message"] if response["success"] == TRUE
      raise response["message"] if response["success"] == FALSE
    rescue Exception => e
      @logger.error("An error occurred while checking if reference exists: " + e.to_s)
      @logger.error(e.backtrace)
      if e.to_s.include? "Unauthorized" or e.to_s.include? "401"
	raise e
      end
      return FALSE
    else
      return res.to_s == 'TRUE'
    end
  end

  def delete(reference)
    @logger.info("Deletion requested for reference #{reference}", print=TRUE)
    checkReferenceChars(reference)
    uri = URI(URI.escape("#{@erp5_url}/ERP5Site_invalidateIngestionObjects?reference=#{reference}"))
    res = handleRequest(uri, nil, nil, non_json_content=true)
    if res["success"] == FALSE
      @logger.abortExecution()
    end
    @logger.info("Remote file successfully ingested.", print=TRUE)
  end

  def rename(reference, new_reference)
    @logger.info("Rename requested for reference #{reference}, new reference #{new_reference}", print=TRUE)
    checkReferenceChars(reference)
    checkReferenceChars(new_reference)
    uri = URI(URI.escape("#{@erp5_url}/ERP5Site_renameIngestion?reference=#{reference}&new_reference=#{new_reference}"))
    res = handleRequest(uri, nil, nil, non_json_content=true)
    if res["success"] == FALSE
      @logger.abortExecution()
    end
    @logger.info("Remote file successfully renamed.", print=TRUE)
  end

  def increaseDatasetVersion(reference)
    if reference == ""
      @logger.warn("Could not increase data set version because dataset reference is empty.")
    else
      @logger.info("Increasing dataset version")
      begin
        uri = URI(URI.escape("#{@erp5_url}/ERP5Site_increaseDatasetVersion?reference=#{reference}"))
        response = handleRequest(uri, nil, nil, non_json_content=true)
        raise response["message"] if response["success"] == FALSE
      rescue Exception => e
	@logger.error("An error occurred while increasing dataset version: " + e.to_s)
	@logger.error(e.backtrace)
      end
    end
  end

  def setDatasetDescription(data_set, description_text)
    puts
    @logger.info("Setting remote dataset description...", print=TRUE)
      begin
	uri = URI(URI.escape("#{@erp5_url}/ERP5Site_setDatasetDescription?dataset=#{data_set}"))
	encoded_description = Base64.encode64(description_text)
	response = handleRequest(uri, reference=nil, data_chunk=encoded_description)
	raise response["message"] if not response["success"]
	dict = JSON.parse(response["message"])
	raise dict["message"] if dict["status_code"] != 0
	@logger.info(dict["message"], print=TRUE)
      rescue Exception => e
	puts
	@logger.error("An error occurred while setting dataset description: " + e.to_s, print=TRUE)
	@logger.error(e.backtrace)
      end
      puts
  end

  def ingest(reference, data_chunk, split)
      @logger.info("Ingestion reference: #{reference}", print=TRUE)
      if split and Time.new - @last_ingestion < 3
	# avoid to send split ingestions to close
	sleep 3
      end
      if exists(reference)
	@logger.info("There is another ingestion already done for the pair dataset-filename. Reference "\
	      + reference, print=TRUE)
	@logger.info("Rename your file or download the full dataset to make local changes.", print=TRUE)
	return {"success"=>FALSE, "message"=>HTTP_REFERENCE_EXIST}
      end
      uri = URI(URI.escape("#{@erp5_url}/ingest?reference=#{reference}"))
      n_retry = 0
      response = handleRequest(uri, reference, data_chunk)
      while response["success"] == FALSE && n_retry < 10
	if response["message"] != HTTP_MESSAGE_EXCEPTION && response["message"] != HTTP_MESSAGE_NOT_2XX
	  return response
	else
	  n_retry += 1
	  @logger.info("Retrying #{n_retry}/10...", print=TRUE)
	  sleep 30
	  response = handleRequest(uri, reference, data_chunk)
	end
      end
      if response["success"] == FALSE
	return response
      end
      @logger.info("Record successfully ingested.", print=TRUE)
      @last_ingestion = Time.new
      return {"success"=>TRUE, "message"=>"success"}
  end

  def eachDataStreamContentChunk(id, chunk_size, n_chunk=0)
    n_part = n_chunk
    done = FALSE
    first = TRUE
    while not done
      start_offset = n_part*chunk_size
      end_offset = n_part*chunk_size+chunk_size
      uri = URI(URI.escape("#{@erp5_url}getDataStreamChunk?id=#{id}&start_offset=#{start_offset}&end_offset=#{end_offset}"))
      success = FALSE
      n_retry = 0
      while ! success && n_retry < 10
        response = handleRequest(uri, nil, nil, non_json_content=true)
        if response["success"] == TRUE
          chunk = response["message"]
          if chunk.nil? || chunk.empty?
            if first
              yield chunk
            end
            @logger.info("Done", print=TRUE)
            done = TRUE
          else
            first = FALSE
            n_part += 1
            yield chunk
          end
          success = TRUE
        else
          @logger.error("Error downloading data: " + response["message"], print=TRUE)
          n_retry += 1
          @logger.info("Retrying #{n_retry}/10...", print=TRUE)
          sleep 30
        end
      end
      raise exception if not success
    end
  end

  def getDataStreams(data_set_reference)
    uri = URI(URI.escape("#{@erp5_url}getDataStreamList?data_set_reference=#{data_set_reference}"))
    response = handleRequest(uri)
    if response["success"] == FALSE
      @logger.abortExecution()
    end
    str = response["message"]
    if not str.nil?
	str.gsub!(/(\,)(\S)/, "\\1 \\2")
    	return YAML::load(str)
    end
    return {'status_code': 0, 'result': []}
  end

  private
  def handleRequest(uri, reference=nil, data_chunk=nil, non_json_content=false)
    scheme = uri.scheme == 'https' ? "https://" : "http://"
    url = uri.to_s.sub! scheme, scheme + @user + ":" + @password + "@"
    begin
      command = "curl --request POST '" + url + "'"
      if data_chunk != nil
        command += " --data 'data_chunk=" + data_chunk + "'"
      end
      stdout,stderr,status = Open3.capture3(command)#) + " >&2")
      if status.success?
        # check response (if ok, it should be empty or json)
        if not stdout.nil? and not stdout.empty?
          begin
            if not non_json_content
              if stdout.include? "'"
                stdout = stdout.gsub! "'", '"'
              end
              dict = JSON.parse(stdout)
            end
          rescue Exception => e
            @logger.error("EXCEPTION: " + e.to_s, print=TRUE)
            @logger.error(HTTP_MESSAGE_400, print=TRUE)
            @logger.error(stdout)
          end
        end
        @logger.info("Done")
        return {"success"=>TRUE, "message"=>stdout}
      else
        @logger.error(HTTP_MESSAGE_OTHER, print=TRUE)
        @logger.error(stderr)
      end
    rescue Exception => e
	    @logger.error("HTTP ERROR: " + e.to_s, print=TRUE)
	    @logger.error(e.backtrace)
      return {"success"=>FALSE, "message"=>e.to_s}
    end
  end
end
