# (c) Copyright IBM Corp. 2021
# (c) Copyright Instana Inc. 2017

require 'test_helper'
require 'rack/test'

if Rack.release >= '3.0.0'
  require 'rackup/lobster'
else
  require 'rack/lobster'
end

require "opentracing"

module Instana
  class OTRack1
    def initialize(app)
      @app = app
    end

    def call(env)
      otrack1_span = OpenTracing.start_span(:otrack1)
      result = @app.call(env)
      otrack1_span.finish
      result
    end
  end

  class OTRack2
    def initialize(app)
      @app = app
    end

    def call(env)
      otrack2_span = OpenTracing.start_span(:otrack2)
      result = @app.call(env)
      otrack2_span.finish
      result
    end
  end
end

class OpenTracerTest < Minitest::Test
  include Rack::Test::Methods

  def app
    @app = Rack::Builder.new {
      use Instana::Rack
      use Instana::OTRack1
      use Rack::CommonLogger
      use Rack::ShowExceptions
      use Instana::OTRack2
      map "/mrlobster" do
        if Rack.release >= '3.0.0'
          run Rackup::Lobster.new
        else
          run Rack::Lobster.new
        end
      end
    }
  end

  def test_supplies_all_ot_interfaces
    clear_all!
    assert defined?(OpenTracing)
    assert OpenTracing.respond_to?(:global_tracer)
    assert OpenTracing.global_tracer.respond_to?(:start_span)
    assert OpenTracing.global_tracer.respond_to?(:inject)
    assert OpenTracing.global_tracer.respond_to?(:extract)

    assert OpenTracing.respond_to?(:start_span)

    assert defined?(OpenTracing::Carrier)
    carrier = OpenTracing::Carrier.new
    assert carrier.respond_to?(:[])
    assert carrier.respond_to?(:[]=)
    assert carrier.respond_to?(:each)

    span = OpenTracing.start_span(:blah)
    assert span.respond_to?(:finish)
    assert span.respond_to?(:set_tag)
    assert span.respond_to?(:tags)
    assert span.respond_to?(:operation_name=)
    assert span.respond_to?(:set_baggage_item)
    assert span.respond_to?(:get_baggage_item)
    assert span.respond_to?(:context)
    assert span.respond_to?(:log)
  end

  def test_basic_get_with_opentracing
    clear_all!
    get '/mrlobster'
    assert last_response.ok?

    spans = ::Instana.processor.queued_spans
    assert_equal 3, spans.length

    first_span = find_first_span_by_name(spans, :rack)
    second_span = find_first_span_by_name(spans, :otrack1)
    third_span = find_first_span_by_name(spans, :otrack2)

    assert_equal :rack, first_span[:n]
    assert first_span[:ts].is_a?(Integer)
    assert first_span[:ts] > 0
    assert first_span[:d].is_a?(Integer)
    assert first_span[:d].between?(0, 5)
    assert first_span.key?(:data)
    assert first_span[:data].key?(:http)
    assert_equal "GET", first_span[:data][:http][:method]
    assert_equal "/mrlobster", first_span[:data][:http][:url]
    assert_equal 200, first_span[:data][:http][:status]
    assert_equal 'example.org', first_span[:data][:http][:host]
    assert_equal :otrack1, second_span[:data][:sdk][:name]
    assert second_span.key?(:data)
    assert second_span[:data].key?(:sdk)
    assert second_span[:data][:sdk].key?(:name)
    assert_equal :otrack2, third_span[:data][:sdk][:name]
    assert third_span.key?(:data)
    assert third_span[:data].key?(:sdk)
    assert third_span[:data][:sdk].key?(:name)

    # ID Validation
    refute_equal first_span[:t], second_span[:t]
    refute_equal second_span[:t], third_span[:t]
  end

  def test_get_with_inject_extract
    clear_all!

    trace_id = ::Instana::Util.generate_id
    span_id = ::Instana::Util.generate_id

    header 'X-Instana-T', ::Instana::Util.id_to_header(trace_id)
    header 'X-Instana-S', ::Instana::Util.id_to_header(span_id)

    get '/mrlobster'
    assert last_response.ok?

    spans = ::Instana.processor.queued_spans

    assert_equal 3, spans.length
    first_span = find_first_span_by_name(spans, :rack)

    # Make sure context was picked up and continued in the resulting
    # trace
    assert_equal trace_id, first_span[:t]
    assert_equal span_id, first_span[:p]
  end

  def test_start_span_with_tags
    clear_all!
    span = OpenTracing.start_span('my_app_entry')

    assert span.is_a?(::Instana::Span)

    span.set_tag(:tag_integer, 1234)
    span.set_tag(:tag_boolean, true)
    span.set_tag(:tag_array, [1,2,3,4])
    span.set_tag(:tag_string, "1234")

    assert_equal 1234, span.tags(:tag_integer)
    assert_equal true, span.tags(:tag_boolean)
    assert_equal [1,2,3,4], span.tags(:tag_array)
    assert_equal "1234", span.tags(:tag_string)
    span.finish
  end

  def test_start_span_with_custom_start_time
    clear_all!
    now = Time.now
    now_in_ms = ::Instana::Util.time_to_ms(now)

    span = OpenTracing.start_span('my_app_entry', :start_time => now)

    assert span.is_a?(::Instana::Span)

    span.set_tag(:tag_integer, 1234)
    span.set_tag(:tag_boolean, true)
    span.set_tag(:tag_array, [1,2,3,4])
    span.set_tag(:tag_string, "1234")

    assert_equal 1234, span.tags(:tag_integer)
    assert_equal true, span.tags(:tag_boolean)
    assert_equal [1,2,3,4], span.tags(:tag_array)
    assert_equal "1234", span.tags(:tag_string)
    span.finish

    assert span[:ts].is_a?(Integer)
    assert_equal now_in_ms, span[:ts]
    assert span[:d].is_a?(Integer)
    assert span[:d].between?(0, 5)
  end

  def test_span_kind_translation
    clear_all!
    span = OpenTracing.start_span('my_app_entry')

    assert span.is_a?(::Instana::Span)

    span.set_tag(:'span.kind', :server)
    assert_equal :entry, span[:data][:sdk][:type]
    assert_equal 1, span[:k]

    span.set_tag(:'span.kind', :consumer)
    assert_equal :entry, span[:data][:sdk][:type]
    assert_equal 1, span[:k]

    span.set_tag(:'span.kind', :client)
    assert_equal :exit, span[:data][:sdk][:type]
    assert_equal 2, span[:k]

    span.set_tag(:'span.kind', :producer)
    assert_equal :exit, span[:data][:sdk][:type]
    assert_equal 2, span[:k]

    span[:data][:sdk].delete(:type)
    span.set_tag(:'span.kind', :blah)
    assert_equal :intermediate, span[:data][:sdk][:type]
    assert_equal 3, span[:k]
    assert_equal :blah, span[:data][:sdk][:custom][:tags][:'span.kind']

    span.finish
  end

  def test_start_span_with_baggage
    clear_all!
    span = OpenTracing.start_span('my_app_entry')
    span.set_baggage_item(:baggage_integer, 1234)
    span.set_baggage_item(:baggage_boolean, false)
    span.set_baggage_item(:baggage_array, [1,2,3,4])
    span.set_baggage_item(:baggage_string, '1234')

    assert_equal 1234, span.get_baggage_item(:baggage_integer)
    assert_equal false, span.get_baggage_item(:baggage_boolean)
    assert_equal [1,2,3,4], span.get_baggage_item(:baggage_array)
    assert_equal "1234", span.get_baggage_item(:baggage_string)
    span.finish
  end

  def test_start_span_with_timestamps
    clear_all!
    span_tags = {:start_tag => 1234, :another_tag => 'tag_value'}

    ts_start = Time.now - 1 # Put start time a bit in the past
    ts_start_ms = ::Instana::Util.time_to_ms(ts_start)

    span = OpenTracing.start_span('my_app_entry', tags: span_tags, start_time: ts_start)
    sleep 0.1

    ts_finish = Time.now + 5 # Put end time in the future
    ts_finish_ms = ::Instana::Util.time_to_ms(ts_finish)

    span.finish(ts_finish)

    assert_equal ts_start_ms, span[:ts]
    assert_equal (ts_finish_ms - ts_start_ms), span[:d]

    assert_equal 1234, span[:data][:sdk][:custom][:tags][:start_tag]
    assert_equal 'tag_value', span[:data][:sdk][:custom][:tags][:another_tag]
  end

  def test_nested_spans_using_child_of
    clear_all!
    entry_span = OpenTracing.start_span(:rack)
    ac_span = OpenTracing.start_span(:action_controller, child_of: entry_span)
    av_span = OpenTracing.start_span(:action_view, child_of: ac_span)
    sleep 0.1
    av_span.finish
    ac_span.finish
    entry_span.finish

    spans = ::Instana.processor.queued_spans
    assert_equal 3, spans.length

    first_span = find_first_span_by_name(spans, :rack)
    second_span = find_first_span_by_name(spans, :action_controller)
    third_span = find_first_span_by_name(spans, :action_view)

    # IDs
    assert_equal first_span[:t], second_span[:t]
    assert_equal second_span[:t], third_span[:t]

    # Linkage
    assert first_span[:p].nil?
    assert_equal first_span[:s], second_span[:p]
    assert_equal second_span[:s], third_span[:p]
  end

  def test_nested_spans_with_baggage
    clear_all!
    entry_span = OpenTracing.start_span(:rack)
    ac_span = OpenTracing.start_span(:action_controller, child_of: entry_span)
    ac_span.set_baggage_item(:my_bag, 1)
    av_span = OpenTracing.start_span(:action_view, child_of: ac_span)
    sleep 0.1
    av_span.finish
    ac_span.finish
    entry_span.finish

    spans = ::Instana.processor.queued_spans
    assert_equal 3, spans.length

    first_span = find_first_span_by_name(spans, :rack)
    second_span = find_first_span_by_name(spans, :action_controller)
    third_span = find_first_span_by_name(spans, :action_view)

    # IDs
    assert_equal first_span[:t], second_span[:t]
    assert_equal second_span[:t], third_span[:t]

    # Linkage
    assert first_span[:p].nil?
    assert_equal first_span[:s], second_span[:p]
    assert_equal second_span[:s], third_span[:p]

    # Every span should have baggage
    assert_equal({}, entry_span.context.baggage)
    assert_equal({:my_bag=>1}, ac_span.context.baggage)
    assert_equal({:my_bag=>1}, av_span.context.baggage)
  end

  def test_context_should_carry_baggage
    clear_all!

    entry_span = OpenTracing.start_span(:rack)
    entry_span_context = entry_span.context

    ac_span = OpenTracing.start_span(:action_controller, child_of: entry_span)
    ac_span.set_baggage_item(:my_bag, 1)
    ac_span_context = ac_span.context

    av_span = OpenTracing.start_span(:action_view, child_of: entry_span)
    av_span_context = av_span.context

    sleep 0.1

    av_span.finish
    ac_span.finish
    entry_span.finish

    spans = ::Instana.processor.queued_spans
    assert_equal 3, spans.length

    assert_equal({}, entry_span.context.baggage)
    assert_equal({:my_bag=>1}, ac_span.context.baggage)
    assert_equal({}, av_span.context.baggage)
  end

  def test_start_active_span
    clear_all!

    span = OpenTracing.start_active_span(:rack)
    assert_equal ::Instana::Tracer.current_span, span

    sleep 0.1

    span.finish

    spans = ::Instana.processor.queued_spans
    assert_equal 1, spans.length
  end

  def test_active_span
    clear_all!

    span = OpenTracing.start_active_span(:rack)
    assert_equal OpenTracing.active_span, span
  end

  def test_active_span_block
    clear_all!

    obj = OpenTracing.start_active_span(:rack) { 1 }
    assert_equal 1, obj
  end

  def test_span_rename
    span = OpenTracing.start_active_span(:rack)
    span.operation_name = 'test'
    assert_equal 'test', span.name
  end
end