# This file is distributed under New Relic's license terms.
# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
# frozen_string_literal: true

require_relative '../../../test_helper'
require 'thread'
require 'timeout'
require 'zlib'
require 'new_relic/agent/threading/backtrace_service'
require 'new_relic/agent/threading/threaded_test_case'
require 'new_relic/agent/commands/thread_profiler_session'
require 'new_relic/base64'

module ThreadProfilerSessionTestHelpers
  START = {
    'profile_id' => 42,
    'sample_period' => 0.02,
    'duration' => 0.025,
    'only_runnable_threads' => false,
    'only_request_threads' => false,
    'profile_agent_code' => false
  }

  STOP = {
    'profile_id' => 42,
    'report_data' => true
  }

  STOP_AND_DISCARD = {
    'profile_id' => 42,
    'report_data' => false
  }

  def start_command
    create_agent_command(START)
  end

  def stop_command
    create_agent_command(STOP)
  end

  def stop_and_discard_command
    create_agent_command(STOP_AND_DISCARD)
  end
end

if !NewRelic::Agent::Threading::BacktraceService.is_supported?

  class ThreadProfilerUnsupportedTest < Minitest::Test
    include ThreadProfilerSessionTestHelpers

    def setup
      backtrace_service = NewRelic::Agent::Threading::BacktraceService.new
      @profiler = NewRelic::Agent::Commands::ThreadProfilerSession.new(backtrace_service)
    end

    def test_thread_profiling_isnt_supported
      refute_predicate NewRelic::Agent::Threading::BacktraceService, :is_supported?
    end

    def test_stop_is_safe_when_not_supported
      @profiler.start(start_command)
      @profiler.stop(true)
    end

    def test_wont_start_and_reports_error
      assert_raises NewRelic::Agent::Commands::AgentCommandRouter::AgentCommandError do
        @profiler.handle_start_command(start_command)
      end

      refute_predicate @profiler, :running?
    end
  end

else

  require 'json'

  class ThreadProfilerSessionTest < Minitest::Test
    include ThreadedTestCase
    include ThreadProfilerSessionTestHelpers

    def setup
      setup_fake_threads
      backtrace_service = NewRelic::Agent::Threading::BacktraceService.new
      backtrace_service.worker_loop.stubs(:run).returns(nil)
      @profiler = NewRelic::Agent::Commands::ThreadProfilerSession.new(backtrace_service)
    end

    def teardown
      NewRelic::Agent.instance.stats_engine.clear_stats
      teardown_fake_threads
    end

    def test_is_supported
      assert_predicate NewRelic::Agent::Threading::BacktraceService, :is_supported?
    end

    def test_is_not_running
      assert_false @profiler.running?
    end

    def test_is_running
      @profiler.start(start_command)

      assert_predicate @profiler, :running?
    end

    def test_is_not_ready_to_harvest_if_no_profile_started
      assert_false @profiler.ready_to_harvest?
    end

    def test_is_ready_to_harvest_if_duration_has_elapsed
      nr_freeze_process_time
      @profiler.start(start_command)

      assert_false @profiler.ready_to_harvest?

      advance_process_time(0.026)

      assert_predicate @profiler, :ready_to_harvest?
    end

    def test_can_stop_a_running_profile
      @profiler.start(start_command)

      assert_predicate @profiler, :running?

      @profiler.stop(true)

      assert_false @profiler.running?

      refute_nil @profiler.harvest
    end

    def test_can_stop_a_running_profile_and_discard
      @profiler.start(start_command)

      assert_predicate @profiler, :running?

      @profiler.stop(false)

      assert_nil @profiler.harvest
    end

    def test_wont_crash_if_stopping_when_not_started
      @profiler.stop(true)

      refute_predicate @profiler, :running?
    end

    def test_handle_start_command_starts_running
      @profiler.handle_start_command(start_command)

      assert_predicate @profiler, :running?
    end

    def test_config_can_disable_running
      with_config(:'thread_profiler.enabled' => false) do
        assert_raises NewRelic::Agent::Commands::AgentCommandRouter::AgentCommandError do
          @profiler.handle_start_command(start_command)
        end

        assert_false @profiler.running?
      end
    end

    def test_handle_stop_command
      @profiler.start(start_command)

      assert_predicate @profiler, :running?

      @profiler.handle_stop_command(stop_command)

      assert_false @profiler.running?
    end

    def test_handle_stop_command_and_discard
      @profiler.start(start_command)

      assert_predicate @profiler, :running?

      @profiler.handle_stop_command(stop_and_discard_command)

      assert_nil @profiler.harvest
    end

    def test_handle_start_command_wont_start_second_profile
      @profiler.handle_start_command(start_command)
      original_profile = @profiler.instance_variable_get(:@profile)

      begin
        @profiler.handle_start_command(start_command)
      rescue NewRelic::Agent::Commands::AgentCommandRouter::AgentCommandError
      end

      if original_profile.nil?
        assert_nil @profiler.harvest
      else
        assert_equal original_profile, @profiler.harvest
      end
    end

    def test_start_command_sent_twice_raises_error
      @profiler.handle_start_command(start_command)

      assert_raises NewRelic::Agent::Commands::AgentCommandRouter::AgentCommandError do
        @profiler.handle_start_command(start_command)
      end
    end

    def test_harvested_profile_doesnt_still_report_as_ready_to_harvest
      nr_freeze_process_time
      @profiler.handle_start_command(start_command)

      advance_process_time(1.0)
      @profiler.stop(true)

      assert_predicate @profiler, :ready_to_harvest?

      @profiler.harvest

      assert_false @profiler.ready_to_harvest?
    end

    def test_command_attributes_passed_along
      @profiler.handle_start_command(start_command)
      @profiler.handle_stop_command(stop_command)
      profile = @profiler.harvest

      assert_equal 42, profile.profile_id
      assert_in_delta(0.02, profile.requested_period)
    end
  end
end
