class PriceApi
  PRICEAPI_BASE_URL = "https://api.priceapi.com"
  REFRESH_INTERVAL = 30.seconds

  def self.token=(token)
    Thread.current[:price_api_token] = token
  end

  def self.token
    raise "Set PriceApi.token first!" if Thread.current[:price_api_token].nil?
    return Thread.current[:price_api_token]
  end

  def self.bulk_request(source, country, key, values, completeness = "one_page", currentness = "daily_updated", retries = 3)
    job_id, error = create_bulk_request(source, country, key, values, completeness, currentness, retries)

    if job_id.nil?
      puts "Failed to create bulk request: #{error}"
      return nil
    end

    sleep REFRESH_INTERVAL until check_status(job_id, retries) == "finished"

    json = download_results(job_id, retries)
    return JSON.parse(json)
  end

  def self.create_bulk_request(source, country, key, values, completeness = "one_page", currentness = "daily_updated", retries = 3)
    values = [values] unless values.is_a?(Array)
    raise "Too many values, max 1,000" if values.size > 1000

    uri = URI("#{PRICEAPI_BASE_URL}/jobs")
    try = 1

    form_data = {
      token: token,
      source: source,
      country: country,
      key: key,
      values: values.join("\n"),
      completeness: completeness,
      currentness: currentness
    }

    body = send_post(uri, form_data)

    response = JSON.parse(body)
    puts "API response: #{response.inspect}"

    if response["success"] == false
      case response["reason"]
      when "unauthorized"
        puts "#{response["reason"]} - token is not valid"
      when "missing parameter"
        puts "#{response["reason"]} - not all required parameters were given"
      when "parameter value invalid"
        puts "#{response["reason"]} - given value is not allowed for this parameter"
      when "unsupported source"
        puts "#{response["reason"]} - chosen source is not supported (yet)"
      when "unsupported country"
        puts "#{response["reason"]} - chosen country is not supported for the chosen source"
      when "unsupported key"
        puts "#{response["reason"]} - chosen key is not supported by the chosen source"
      when "not enough free credits"
        puts "#{response["reason"]} - no free credits left, a paid subscription is needed"
      when "daily quota exceeded"
        puts "#{response["reason"]} - user defined daily quota is exceeded; you can change this under quota settings."
      when "job not found"
        puts "#{response["reason"]} - no bulk request could found for given job_id and token"
      when "job not finished"
        puts "#{response["reason"]} - bulk request is still in progress; wait until it is finished."
      else
        puts response["reason"]
      end

      return nil, response["reason"]
    else
      puts "Success. job_id: #{response["job_id"].inspect}"

      return response["job_id"], nil
    end
  end

  def self.check_status(job_id, retries = 3)
    uri = URI("#{PRICEAPI_BASE_URL}/jobs/#{job_id}?token=#{token}")

    body = send_get(uri, retries)
    response = JSON.parse(body)
    puts "API response: #{response.inspect}"

    return response["status"]
  end

  def self.download_results(job_id, format = "json", retries = 3)
    uri = URI("#{PRICEAPI_BASE_URL}/products/bulk/#{job_id}.#{format}?token=#{token}")

    body = send_get(uri, retries)
    response = JSON.parse(body)
    puts "API response: #{response.inspect}"

    return response["status"]
  end

  def self.send_post(uri, form_data, retries = 3)
    puts "Send POST to #{uri}: #{form_data.inspect}"

    try = 1
    begin
      req = Net::HTTP::Post.new(uri)
      req.set_form_data(form_data)

      res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
        http.read_timeout = 60
        http.request(req)
      end
      puts "HTTP response: #{res.code}"

      return res.body
    rescue SystemCallError, Timeout::Error, Net::ProtocolError, IOError => e
      puts "#{e.class.name}: #{e.message}"
      retry if (try += 1) <= retries
      raise e
    end
  end

  def self.send_get(uri, retries = 3)
    puts "Send GET to #{uri}"

    try = 1
    begin
      http = Net::HTTP.new(uri.hostname, uri.port)
      http.use_ssl = true
      http.read_timeout = 15 * 60
      res = http.get(uri.request_uri)
      puts "HTTP response: #{res.code}"

      return res.body
    rescue SystemCallError, Timeout::Error, Net::ProtocolError, IOError => e
      puts "#{e.class.name}: #{e.message}"
      retry if (try += 1) <= 3
      raise e
    end
  end
end