defmodule SnmpKit.SnmpLib.ASN1 do
  @moduledoc """
  Comprehensive ASN.1 BER (Basic Encoding Rules) encoding and decoding utilities.

  Provides low-level ASN.1 operations that are used by the PDU module and can be
  used for other ASN.1 encoding/decoding needs. This module offers improved
  performance and better error handling compared to basic implementations.

  ## Features

  - Complete BER encoding/decoding support
  - Optimized length handling for large values
  - Comprehensive error reporting
  - Support for constructed and primitive types
  - Memory-efficient implementations
  - Validation and constraint checking
  - RFC-compliant OID multibyte encoding (values ≥ 128)

  ## ASN.1 Types Supported

  - **INTEGER**: Signed integers with arbitrary precision
  - **OCTET STRING**: Binary data of any length
  - **NULL**: Null value representation
  - **OBJECT IDENTIFIER**: Hierarchical object identifiers with multibyte subidentifiers
  - **SEQUENCE**: Constructed type for complex structures
  - **Custom Tags**: Application-specific and context-specific tags

  ## Important: OID Encoding

  OID subidentifiers use 7-bit encoding where values ≥ 128 require multibyte encoding:

  - Values 0-127: Single byte
  - Values 128+: Multibyte with continuation bits
  - Example: 200 → `[0x81, 0x48]` (not single byte `0xC8`)

  ## Examples

      # Integer encoding/decoding
      iex> {:ok, encoded} = SnmpKit.SnmpLib.ASN1.encode_integer(42)
      iex> {:ok, {value, <<>>}} = SnmpKit.SnmpLib.ASN1.decode_integer(encoded)
      iex> value
      42

      # OCTET STRING encoding/decoding
      iex> {:ok, encoded} = SnmpKit.SnmpLib.ASN1.encode_octet_string("Hello")
      iex> {:ok, {value, <<>>}} = SnmpKit.SnmpLib.ASN1.decode_octet_string(encoded)
      iex> value
      "Hello"

      # OID encoding/decoding with multibyte values
      iex> {:ok, encoded} = SnmpKit.SnmpLib.ASN1.encode_oid([1, 3, 6, 1, 4, 1, 200])
      iex> {:ok, {oid, <<>>}} = SnmpKit.SnmpLib.ASN1.decode_oid(encoded)
      iex> oid
      [1, 3, 6, 1, 4, 1, 200]

      # Length encoding for large values
      iex> encoded_length = SnmpKit.SnmpLib.ASN1.encode_length(1000)
      iex> {:ok, {length, <<>>}} = SnmpKit.SnmpLib.ASN1.decode_length(encoded_length)
      iex> length
      1000

      # NULL encoding
      iex> {:ok, encoded} = SnmpKit.SnmpLib.ASN1.encode_null()
      iex> {:ok, {value, <<>>}} = SnmpKit.SnmpLib.ASN1.decode_null(encoded)
      iex> value
      :null
  """

  import Bitwise

  @type tag :: non_neg_integer()
  @type length :: non_neg_integer()
  @type content :: binary()
  @type tlv :: {tag(), length(), content()}
  @type oid :: [non_neg_integer()]

  # Standard ASN.1 tags
  @tag_integer 0x02
  @tag_octet_string 0x04
  @tag_null 0x05
  @tag_oid 0x06
  @tag_sequence 0x30

  # Length encoding constants
  @short_form_max 127
  @indefinite_length 0x80

  ## Generic Encoding Functions

  @doc """
  Encodes an ASN.1 INTEGER value using BER (Basic Encoding Rules).

  Supports arbitrary precision integers using two's complement representation.
  The encoded result includes the ASN.1 tag (0x02), length, and content bytes.

  ## Parameters

  - `value`: Integer value to encode (any size)

  ## Returns

  - `{:ok, encoded_bytes}` on success
  - `{:error, reason}` on failure

  ## Examples

      # Positive integers
      iex> {:ok, encoded} = SnmpKit.SnmpLib.ASN1.encode_integer(42)
      iex> encoded
      <<2, 1, 42>>

      # Negative integers (two's complement)
      iex> {:ok, encoded} = SnmpKit.SnmpLib.ASN1.encode_integer(-1)
      iex> encoded
      <<2, 1, 255>>

      # Zero
      iex> {:ok, encoded} = SnmpKit.SnmpLib.ASN1.encode_integer(0)
      iex> encoded
      <<2, 1, 0>>

      # Large integers
      iex> {:ok, encoded} = SnmpKit.SnmpLib.ASN1.encode_integer(32767)
      iex> byte_size(encoded) > 3
      true
  """
  @spec encode_integer(integer()) :: {:ok, binary()}
  def encode_integer(value) when is_integer(value) do
    content = encode_integer_content(value)
    tlv_bytes = encode_tlv(@tag_integer, content)
    {:ok, tlv_bytes}
  end

  @doc """
  Encodes an ASN.1 OCTET STRING value.

  ## Parameters

  - `value`: Binary data to encode

  ## Examples

      {:ok, encoded} = SnmpKit.SnmpLib.ASN1.encode_octet_string("Hello")
      {:ok, encoded} = SnmpKit.SnmpLib.ASN1.encode_octet_string(<<1, 2, 3, 4>>)
  """
  @spec encode_octet_string(binary()) :: {:ok, binary()}
  def encode_octet_string(value) when is_binary(value) do
    tlv_bytes = encode_tlv(@tag_octet_string, value)
    {:ok, tlv_bytes}
  end

  def encode_octet_string({:ok, binary}) when is_binary(binary) do
    encode_octet_string(binary)
  end

  def encode_octet_string(value) when not is_binary(value) do
    require Logger

    Logger.error("encode_octet_string received non-binary value: #{inspect(value)}")
    {:error, {:invalid_type, value}}
  end

  @doc """
  Encodes an ASN.1 NULL value.

  ## Examples

      {:ok, <<5, 0>>} = SnmpKit.SnmpLib.ASN1.encode_null()
  """
  @spec encode_null() :: {:ok, binary()}
  def encode_null do
    {:ok, encode_tlv(@tag_null, <<>>)}
  end

  @doc """
  Encodes an ASN.1 OBJECT IDENTIFIER (OID) value using BER encoding.

  OIDs are hierarchical identifiers where each component is encoded using
  7-bit subidentifiers. Values ≥ 128 require multibyte encoding with
  continuation bits, which is correctly handled by this implementation.

  ## Parameters

  - `oid_list`: List of non-negative integers representing the OID (minimum 2 components)

  ## Returns

  - `{:ok, encoded_bytes}` on success
  - `{:error, reason}` on failure (invalid OID format)

  ## Encoding Rules

  - First two components are combined: `first * 40 + second`
  - Remaining components use 7-bit encoding with continuation bits
  - Values 0-127: single byte
  - Values 128+: multibyte with high bit indicating continuation

  ## Examples

      # Standard SNMP OID (sysDescr.0)
      iex> {:ok, encoded} = SnmpKit.SnmpLib.ASN1.encode_oid([1, 3, 6, 1, 2, 1, 1, 1, 0])
      iex> {:ok, {decoded, <<>>}} = SnmpKit.SnmpLib.ASN1.decode_oid(encoded)
      iex> decoded
      [1, 3, 6, 1, 2, 1, 1, 1, 0]

      # OID with multibyte values (≥ 128)
      iex> {:ok, encoded} = SnmpKit.SnmpLib.ASN1.encode_oid([1, 3, 6, 1, 4, 1, 200])
      iex> {:ok, {decoded, <<>>}} = SnmpKit.SnmpLib.ASN1.decode_oid(encoded)
      iex> decoded
      [1, 3, 6, 1, 4, 1, 200]

      # Invalid OIDs
      iex> SnmpKit.SnmpLib.ASN1.encode_oid([])
      {:error, :invalid_oid}

      iex> SnmpKit.SnmpLib.ASN1.encode_oid([1])
      {:ok, <<6, 1, 1>>}
  """
  @spec encode_oid(oid()) :: {:ok, binary()} | {:error, atom()}
  def encode_oid([_ | _] = oid_list) when is_list(oid_list) do
    case encode_oid_content(oid_list) do
      {:ok, content} ->
        tlv_bytes = encode_tlv(@tag_oid, content)
        {:ok, tlv_bytes}

      {:error, reason} ->
        {:error, reason}
    end
  end

  def encode_oid(_), do: {:error, :invalid_oid}

  @doc """
  Encodes an ASN.1 SEQUENCE with the given content.

  ## Parameters

  - `content`: Pre-encoded content for the sequence

  ## Examples

      content = encode_integer_content(42) <> encode_octet_string_content("test")
      {:ok, sequence} = SnmpKit.SnmpLib.ASN1.encode_sequence(content)
  """
  @spec encode_sequence(binary()) :: {:ok, binary()}
  def encode_sequence(content) when is_binary(content) do
    {:ok, encode_tlv(@tag_sequence, content)}
  end

  @doc """
  Encodes a custom TLV (Tag-Length-Value) structure.

  ## Parameters

  - `tag`: ASN.1 tag value
  - `content`: Binary content

  ## Examples

      {:ok, tlv} = SnmpKit.SnmpLib.ASN1.encode_custom_tlv(0xA0, <<"custom_content">>)
  """
  @spec encode_custom_tlv(tag(), binary()) :: {:ok, binary()}
  def encode_custom_tlv(tag, content) when is_integer(tag) and is_binary(content) do
    {:ok, encode_tlv(tag, content)}
  end

  ## Generic Decoding Functions

  @doc """
  Decodes an ASN.1 INTEGER value.

  ## Parameters

  - `data`: Binary data starting with an INTEGER TLV

  ## Returns

  - `{:ok, {value, remaining_data}}` on success
  - `{:error, reason}` on failure

  ## Examples

      {:ok, {42, remaining}} = SnmpKit.SnmpLib.ASN1.decode_integer(<<2, 1, 42, 99, 100>>)
      # Returns {42, <<99, 100>>}
  """
  @spec decode_integer(binary()) :: {:ok, {integer(), binary()}} | {:error, atom()}
  def decode_integer(<<@tag_integer, rest::binary>>) do
    case decode_length_and_content(rest) do
      {:ok, {content, remaining}} ->
        case decode_integer_content(content) do
          {:ok, value} -> {:ok, {value, remaining}}
          {:error, reason} -> {:error, reason}
        end

      {:error, reason} ->
        {:error, reason}
    end
  end

  def decode_integer(_), do: {:error, :invalid_tag}

  @doc """
  Decodes an ASN.1 OCTET STRING value.

  ## Examples

      {:ok, {"Hello", remaining}} = SnmpKit.SnmpLib.ASN1.decode_octet_string(encoded_data)
  """
  @spec decode_octet_string(binary()) :: {:ok, {binary(), binary()}} | {:error, atom()}
  def decode_octet_string(<<@tag_octet_string, rest::binary>>) do
    case decode_length_and_content(rest) do
      {:ok, {content, remaining}} -> {:ok, {content, remaining}}
      {:error, reason} -> {:error, reason}
    end
  end

  def decode_octet_string(_), do: {:error, :invalid_tag}

  @doc """
  Decodes an ASN.1 NULL value.

  ## Examples

      {:ok, {:null, remaining}} = SnmpKit.SnmpLib.ASN1.decode_null(<<5, 0, 1, 2, 3>>)
      # Returns {:null, <<1, 2, 3>>}
  """
  @spec decode_null(binary()) :: {:ok, {:null, binary()}} | {:error, atom()}
  def decode_null(<<@tag_null, 0, rest::binary>>) do
    {:ok, {:null, rest}}
  end

  def decode_null(<<@tag_null, _::binary>>), do: {:error, :invalid_null_length}
  def decode_null(_), do: {:error, :invalid_tag}

  @doc """
  Decodes an ASN.1 OBJECT IDENTIFIER value.

  ## Examples

      {:ok, {[1, 3, 6, 1], remaining}} = SnmpKit.SnmpLib.ASN1.decode_oid(encoded_data)
  """
  @spec decode_oid(binary()) :: {:ok, {oid(), binary()}} | {:error, atom()}
  def decode_oid(<<@tag_oid, rest::binary>>) do
    case decode_length_and_content(rest) do
      {:ok, {content, remaining}} ->
        case decode_oid_content(content) do
          {:ok, oid_list} -> {:ok, {oid_list, remaining}}
          {:error, reason} -> {:error, reason}
        end

      {:error, reason} ->
        {:error, reason}
    end
  end

  def decode_oid(_), do: {:error, :invalid_tag}

  @doc """
  Decodes an ASN.1 SEQUENCE value.

  Returns the content of the sequence without further parsing.

  ## Examples

      {:ok, {sequence_content, remaining}} = SnmpKit.SnmpLib.ASN1.decode_sequence(encoded_data)
  """
  @spec decode_sequence(binary()) :: {:ok, {binary(), binary()}} | {:error, atom()}
  def decode_sequence(<<@tag_sequence, rest::binary>>) do
    case decode_length_and_content(rest) do
      {:ok, {content, remaining}} -> {:ok, {content, remaining}}
      {:error, reason} -> {:error, reason}
    end
  end

  def decode_sequence(_), do: {:error, :invalid_tag}

  @doc """
  Decodes a generic TLV structure.

  ## Parameters

  - `data`: Binary data starting with a TLV

  ## Returns

  - `{:ok, {tag, content, remaining}}` on success
  - `{:error, reason}` on failure

  ## Examples

      {:ok, {tag, content, remaining}} = SnmpKit.SnmpLib.ASN1.decode_tlv(binary_data)
  """
  @spec decode_tlv(binary()) :: {:ok, {tag(), binary(), binary()}} | {:error, atom()}
  def decode_tlv(<<tag, rest::binary>>) when is_integer(tag) do
    case decode_length_and_content(rest) do
      {:ok, {content, remaining}} -> {:ok, {tag, content, remaining}}
      {:error, reason} -> {:error, reason}
    end
  end

  def decode_tlv(_), do: {:error, :insufficient_data}

  ## Length Encoding/Decoding

  @doc """
  Encodes an ASN.1 length field.

  Supports both short form (< 128) and long form encoding.

  ## Parameters

  - `length`: Length value to encode

  ## Returns

  - Binary representation of the length

  ## Examples

      <<42>> = SnmpKit.SnmpLib.ASN1.encode_length(42)
      <<0x81, 200>> = SnmpKit.SnmpLib.ASN1.encode_length(200)
      <<0x82, 1, 44>> = SnmpKit.SnmpLib.ASN1.encode_length(300)
  """
  @spec encode_length(length()) :: binary()
  def encode_length(length) when is_integer(length) and length >= 0 do
    cond do
      length <= @short_form_max ->
        # Short form: length fits in 7 bits
        <<length>>

      length < 256 ->
        # Long form: 1 byte for length
        <<0x81, length>>

      length < 65_536 ->
        # Long form: 2 bytes for length
        <<0x82, length::16>>

      length < 16_777_216 ->
        # Long form: 3 bytes for length
        <<0x83, length::24>>

      true ->
        # Long form: 4 bytes for length (handles up to ~4GB)
        <<0x84, length::32>>
    end
  end

  @doc """
  Decodes an ASN.1 length field.

  ## Parameters

  - `data`: Binary data starting with a length field

  ## Returns

  - `{:ok, {length, remaining_data}}` on success
  - `{:error, reason}` on failure

  ## Examples

      {:ok, {42, remaining}} = SnmpKit.SnmpLib.ASN1.decode_length(<<42, 1, 2, 3>>)
      {:ok, {300, remaining}} = SnmpKit.SnmpLib.ASN1.decode_length(<<0x82, 1, 44, 1, 2, 3>>)
  """
  @spec decode_length(binary()) :: {:ok, {length(), binary()}} | {:error, atom()}
  def decode_length(<<length_byte, rest::binary>>) when length_byte <= @short_form_max do
    # Short form
    {:ok, {length_byte, rest}}
  end

  def decode_length(<<length_byte, rest::binary>>) when length_byte > @short_form_max do
    cond do
      length_byte == @indefinite_length ->
        {:error, :indefinite_length_not_supported}

      length_byte - @indefinite_length > 4 ->
        {:error, :length_too_large}

      true ->
        num_octets = length_byte - @indefinite_length
        decode_long_form_length(rest, num_octets)
    end
  end

  def decode_length(_), do: {:error, :insufficient_data}

  defp decode_long_form_length(data, num_octets) do
    case data do
      <<length_bytes::binary-size(num_octets), remaining::binary>> ->
        length = :binary.decode_unsigned(length_bytes, :big)
        {:ok, {length, remaining}}

      _ ->
        {:error, :insufficient_length_bytes}
    end
  end

  ## Tag Parsing

  @doc """
  Parses an ASN.1 tag byte.

  Returns information about the tag including class, constructed bit, and tag number.

  ## Parameters

  - `tag_byte`: Single byte representing the tag

  ## Returns

  - Map with tag information

  ## Examples

      info = SnmpKit.SnmpLib.ASN1.parse_tag(0x30)
      # Returns %{class: :universal, constructed: true, tag_number: 16}
  """
  @spec parse_tag(tag()) :: map()
  def parse_tag(tag_byte) when is_integer(tag_byte) and tag_byte >= 0 and tag_byte <= 255 do
    class =
      case (tag_byte &&& 0xC0) >>> 6 do
        0 -> :universal
        1 -> :application
        2 -> :context
        3 -> :private
      end

    constructed = (tag_byte &&& 0x20) != 0
    tag_number = tag_byte &&& 0x1F

    %{
      class: class,
      constructed: constructed,
      tag_number: tag_number,
      original_byte: tag_byte
    }
  end

  ## Validation and Utilities

  @doc """
  Validates the structure of BER-encoded data.

  Performs basic validation without full decoding.

  ## Parameters

  - `data`: BER-encoded binary data

  ## Returns

  - `:ok` if structure is valid
  - `{:error, reason}` if structure is invalid

  ## Examples

      :ok = SnmpKit.SnmpLib.ASN1.validate_ber_structure(valid_ber_data)
      {:error, :invalid_length} = SnmpKit.SnmpLib.ASN1.validate_ber_structure(malformed_data)
  """
  @spec validate_ber_structure(binary()) :: :ok | {:error, atom()}
  def validate_ber_structure(data) when is_binary(data) do
    validate_ber_recursive(data)
  end

  @doc """
  Calculates the total length of a BER-encoded structure.

  ## Parameters

  - `data`: BER-encoded binary data

  ## Returns

  - `{:ok, total_length}` on success
  - `{:error, reason}` on failure

  ## Examples

      {:ok, 10} = SnmpKit.SnmpLib.ASN1.calculate_ber_length(ber_data)
  """
  @spec calculate_ber_length(binary()) :: {:ok, non_neg_integer()} | {:error, atom()}
  def calculate_ber_length(<<tag, rest::binary>>) when is_integer(tag) do
    case decode_length(rest) do
      {:ok, {content_length, _}} ->
        tag_length = 1

        length_field_length =
          byte_size(rest) - byte_size(rest) +
            byte_size(encode_length(content_length))

        total_length = tag_length + length_field_length + content_length
        {:ok, total_length}

      {:error, reason} ->
        {:error, reason}
    end
  end

  def calculate_ber_length(_), do: {:error, :insufficient_data}

  ## Private Helper Functions

  # TLV encoding
  defp encode_tlv(tag, content) when is_integer(tag) and is_binary(content) do
    length_bytes = encode_length(byte_size(content))
    <<tag, length_bytes::binary, content::binary>>
  end

  # Integer content encoding
  defp encode_integer_content(0), do: <<0>>

  defp encode_integer_content(value) when value > 0 do
    bytes = encode_positive_integer(value, [])
    # Check if MSB is set (would be interpreted as negative)
    case bytes do
      <<msb, _::binary>> when msb >= 128 -> <<0, bytes::binary>>
      _ -> bytes
    end
  end

  defp encode_integer_content(value) when value < 0 do
    # Two's complement encoding for negative numbers
    positive_value = abs(value)
    # +1 for sign bit
    bit_length = calculate_bit_length(positive_value) + 1
    # Round up to byte boundary
    byte_length = div(bit_length + 7, 8)

    max_positive = 1 <<< (byte_length * 8 - 1)

    byte_length =
      if positive_value > max_positive do
        # Need one more byte
        byte_length + 1
      else
        byte_length
      end

    # Calculate two's complement
    twos_complement = (1 <<< (byte_length * 8)) + value
    encode_unsigned_integer(twos_complement, byte_length)
  end

  defp encode_positive_integer(0, acc), do: :binary.list_to_bin(acc)

  defp encode_positive_integer(value, acc) do
    encode_positive_integer(value >>> 8, [value &&& 0xFF | acc])
  end

  defp encode_unsigned_integer(value, byte_length) do
    <<value::size(byte_length)-unit(8)-big>>
  end

  defp calculate_bit_length(value) when value == 0, do: 1

  defp calculate_bit_length(value) when value > 0 do
    value |> :math.log2() |> :math.ceil() |> trunc()
  end

  # Integer content decoding
  defp decode_integer_content(<<>>), do: {:error, :empty_integer}
  defp decode_integer_content(<<byte>>) when byte < 128, do: {:ok, byte}
  defp decode_integer_content(<<byte>>) when byte >= 128, do: {:ok, byte - 256}

  defp decode_integer_content(data) when byte_size(data) > 1 do
    <<msb, _::binary>> = data

    if msb >= 128 do
      # Negative number (two's complement)
      bit_size = byte_size(data) * 8
      unsigned_value = :binary.decode_unsigned(data, :big)
      signed_value = unsigned_value - (1 <<< bit_size)
      {:ok, signed_value}
    else
      # Positive number
      value = :binary.decode_unsigned(data, :big)
      {:ok, value}
    end
  end

  # OID content encoding
  defp encode_oid_content([first]) when first < 3 do
    # Single component OID - encode directly
    {:ok, encode_oid_subidentifier(first)}
  end

  defp encode_oid_content([first, second | rest]) when first < 3 and second < 40 do
    first_byte = first * 40 + second
    first_encoded = encode_oid_subidentifier(first_byte)
    rest_encoded = Enum.map(rest, &encode_oid_subidentifier/1)
    content = :binary.list_to_bin([first_encoded | rest_encoded])
    {:ok, content}
  end

  defp encode_oid_content(_), do: {:error, :invalid_oid}

  defp encode_oid_subidentifier(value) when value < 128 do
    <<value>>
  end

  defp encode_oid_subidentifier(value) when value >= 128 do
    encode_oid_large_value(value)
  end

  defp encode_oid_large_value(value) when value >= 128 do
    encode_oid_multibyte(value)
  end

  defp encode_oid_multibyte(value) do
    # Build the multibyte encoding by collecting 7-bit chunks
    bytes = build_multibyte_chunks(value, [])
    # All bytes except the last need continuation bit set
    {leading_bytes, [last_byte]} = Enum.split(bytes, length(bytes) - 1)
    continuation_bytes = Enum.map(leading_bytes, fn byte -> byte ||| 0x80 end)
    :binary.list_to_bin(continuation_bytes ++ [last_byte])
  end

  defp build_multibyte_chunks(value, acc) when value < 128 do
    [value | acc]
  end

  defp build_multibyte_chunks(value, acc) do
    seven_bits = value &&& 0x7F
    remaining = value >>> 7
    build_multibyte_chunks(remaining, [seven_bits | acc])
  end

  # OID content decoding
  defp decode_oid_content(<<first_byte, rest::binary>>) do
    first_subid = div(first_byte, 40)
    second_subid = rem(first_byte, 40)

    case decode_oid_subidentifiers(rest, []) do
      {:ok, remaining_subids} ->
        {:ok, [first_subid, second_subid | remaining_subids]}

      {:error, reason} ->
        {:error, reason}
    end
  end

  defp decode_oid_content(_), do: {:error, :invalid_oid_content}

  defp decode_oid_subidentifiers(<<>>, acc) do
    {:ok, Enum.reverse(acc)}
  end

  defp decode_oid_subidentifiers(data, acc) do
    case decode_oid_subidentifier(data, 0) do
      {:ok, {subid, remaining}} ->
        decode_oid_subidentifiers(remaining, [subid | acc])

      {:error, reason} ->
        {:error, reason}
    end
  end

  defp decode_oid_subidentifier(<<byte, rest::binary>>, acc) do
    new_acc = (acc <<< 7) + (byte &&& 0x7F)

    if (byte &&& 0x80) == 0 do
      # Final byte
      {:ok, {new_acc, rest}}
    else
      # Continue reading
      decode_oid_subidentifier(rest, new_acc)
    end
  end

  defp decode_oid_subidentifier(<<>>, _), do: {:error, :incomplete_subidentifier}

  # Length and content decoding
  defp decode_length_and_content(data) do
    case decode_length(data) do
      {:ok, {length, rest}} ->
        if byte_size(rest) >= length do
          content = binary_part(rest, 0, length)
          remaining = binary_part(rest, length, byte_size(rest) - length)
          {:ok, {content, remaining}}
        else
          {:error, :insufficient_content}
        end

      {:error, reason} ->
        {:error, reason}
    end
  end

  # BER structure validation
  defp validate_ber_recursive(<<>>), do: :ok

  defp validate_ber_recursive(data) do
    case decode_tlv(data) do
      {:ok, {_tag, _content, remaining}} ->
        validate_ber_recursive(remaining)

      {:error, reason} ->
        {:error, reason}
    end
  end
end
