# frozen_string_literal: true

require_relative 'result'

module Datadog
  module AppSec
    module SecurityEngine
      # A class that check input via security engine (WAF) and respond with result.
      class Runner
        SUCCESSFUL_EXECUTION_CODES = [:ok, :match].freeze

        attr_reader :ruleset_version

        def initialize(handle_ref, ruleset_version:)
          @mutex = Mutex.new
          @handle_ref = handle_ref
          @waf_handle = handle_ref.acquire
          @ruleset_version = ruleset_version

          @debug_tag = "libddwaf:#{WAF::VERSION::STRING} method:ddwaf_run"
        end

        def run(persistent_data, ephemeral_data, timeout = WAF::LibDDWAF::DDWAF_RUN_TIMEOUT)
          @mutex.lock

          start_ns = Core::Utils::Time.get_time(:nanosecond)
          persistent_data.reject! do |_, v|
            next false if v.is_a?(TrueClass) || v.is_a?(FalseClass)

            v.nil? || v.empty?
          end

          ephemeral_data.reject! do |_, v|
            next false if v.is_a?(TrueClass) || v.is_a?(FalseClass)

            v.nil? || v.empty?
          end

          result = try_run(persistent_data, ephemeral_data, timeout)
          stop_ns = Core::Utils::Time.get_time(:nanosecond)

          report_execution(result)

          unless SUCCESSFUL_EXECUTION_CODES.include?(result.status)
            return Result::Error.new(duration_ext_ns: stop_ns - start_ns, input_truncated: result.input_truncated?)
          end

          klass = (result.status == :match) ? Result::Match : Result::Ok
          klass.new(
            events: result.events,
            actions: result.actions,
            attributes: result.attributes,
            keep: result.keep?,
            timeout: result.timeout?,
            duration_ns: result.duration,
            duration_ext_ns: (stop_ns - start_ns),
            input_truncated: result.input_truncated?
          )
        ensure
          @mutex.unlock
        end

        def waf_context
          @waf_context ||= @waf_handle.build_context
        end

        def waf_addresses
          @waf_handle.known_addresses
        end

        def finalize!
          @waf_context&.finalize!
        ensure
          @handle_ref.release(@waf_handle)
        end

        private

        def try_run(persistent_data, ephemeral_data, timeout)
          waf_context.run(persistent_data, ephemeral_data, timeout)
        rescue WAF::LibDDWAFError => e
          Datadog.logger.debug { "#{@debug_tag} execution error: #{e} backtrace: #{e.backtrace&.first(3)}" }
          AppSec.telemetry.report(e, description: 'libddwaf-rb internal low-level error')

          WAF::Result.new(
            status: :err_internal,
            events: [],
            actions: {},
            attributes: {},
            duration: 0,
            keep: false,
            timeout: false
          )
        end

        def report_execution(result)
          Datadog.logger.debug { "#{@debug_tag} execution timed out: #{result.inspect}" } if result.timeout?

          if SUCCESSFUL_EXECUTION_CODES.include?(result.status)
            Datadog.logger.debug { "#{@debug_tag} execution result: #{result.inspect}" }
          else
            message = "#{@debug_tag} execution error: #{result.status.inspect}"

            Datadog.logger.debug { message }
            AppSec.telemetry.error(message)
          end
        end
      end
    end
  end
end
