# frozen_string_literal: true

module Sentry
  # @api private
  class Envelope
    class Item
      STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD = 500
      MAX_SERIALIZED_PAYLOAD_SIZE = 1024 * 1000

      attr_accessor :headers, :payload

      def initialize(headers, payload)
        @headers = headers
        @payload = payload
      end

      def type
        @headers[:type] || 'event'
      end

      # rate limits and client reports use the data_category rather than envelope item type
      def self.data_category(type)
        case type
        when 'session', 'attachment', 'transaction', 'profile' then type
        when 'sessions' then 'session'
        when 'check_in' then 'monitor'
        when 'statsd', 'metric_meta' then 'metric_bucket'
        when 'event' then 'error'
        when 'client_report' then 'internal'
        else 'default'
        end
      end

      def data_category
        self.class.data_category(type)
      end

      def to_s
        [JSON.generate(@headers), @payload.is_a?(String) ? @payload : JSON.generate(@payload)].join("\n")
      end

      def serialize
        result = to_s

        if result.bytesize > MAX_SERIALIZED_PAYLOAD_SIZE
          remove_breadcrumbs!
          result = to_s
        end

        if result.bytesize > MAX_SERIALIZED_PAYLOAD_SIZE
          reduce_stacktrace!
          result = to_s
        end

        [result, result.bytesize > MAX_SERIALIZED_PAYLOAD_SIZE]
      end

      def size_breakdown
        payload.map do |key, value|
          "#{key}: #{JSON.generate(value).bytesize}"
        end.join(", ")
      end

      private

      def remove_breadcrumbs!
        if payload.key?(:breadcrumbs)
          payload.delete(:breadcrumbs)
        elsif payload.key?("breadcrumbs")
          payload.delete("breadcrumbs")
        end
      end

      def reduce_stacktrace!
        if exceptions = payload.dig(:exception, :values) || payload.dig("exception", "values")
          exceptions.each do |exception|
            # in most cases there is only one exception (2 or 3 when have multiple causes), so we won't loop through this double condition much
            traces = exception.dig(:stacktrace, :frames) || exception.dig("stacktrace", "frames")

            if traces && traces.size > STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD
              size_on_both_ends = STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD / 2
              traces.replace(
                traces[0..(size_on_both_ends - 1)] + traces[-size_on_both_ends..-1],
              )
            end
          end
        end
      end
    end

    attr_accessor :headers, :items

    def initialize(headers = {})
      @headers = headers
      @items = []
    end

    def add_item(headers, payload)
      @items << Item.new(headers, payload)
    end

    def item_types
      @items.map(&:type)
    end

    def event_id
      @headers[:event_id]
    end
  end
end
