defmodule SnmpKit.SnmpLib.PDU.EncoderTest do
  use ExUnit.Case, async: true

  alias SnmpKit.SnmpLib.PDU.Encoder

  describe "encode_message/1" do
    test "encodes SNMPv1 message" do
      message = %{
        version: 0,
        community: "public",
        pdu: %{
          type: :get_request,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 1, 1, 0], :null, nil}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
      # Should start with SEQUENCE tag
      assert <<0x30, _::binary>> = encoded
    end

    test "encodes SNMPv2c message" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_request,
          request_id: 100,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 1, 5, 0], :null, nil}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "delegates SNMPv3 messages to V3Encoder" do
      message = %{
        version: 3,
        msgID: 1,
        msgMaxSize: 65_507,
        msgFlags: <<0x04>>,
        msgSecurityModel: 3,
        msgSecurityParameters: "",
        contextEngineID: "",
        contextName: "",
        pdu: %{
          type: :get_request,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: []
        }
      }

      # Should delegate to V3Encoder (may fail if V3Encoder requires specific fields)
      result = Encoder.encode_message(message)
      assert is_tuple(result) and elem(result, 0) in [:ok, :error]
    end

    test "returns error for invalid message format" do
      assert {:error, :invalid_message_format} = Encoder.encode_message(%{invalid: :format})
      assert {:error, :invalid_message_format} = Encoder.encode_message(%{version: 1})
      assert {:error, :invalid_message_format} = Encoder.encode_message(%{})
    end

    test "encodes message with multiple varbinds" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_request,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [
            {[1, 3, 6, 1, 2, 1, 1, 1, 0], :null, nil},
            {[1, 3, 6, 1, 2, 1, 1, 3, 0], :null, nil},
            {[1, 3, 6, 1, 2, 1, 1, 5, 0], :null, nil}
          ]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes message with empty community string" do
      message = %{
        version: 0,
        community: "",
        pdu: %{
          type: :get_request,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: []
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end
  end

  describe "encode_message/2" do
    test "delegates SNMPv3 messages with user to V3Encoder" do
      message = %{
        version: 3,
        msgID: 1,
        msgMaxSize: 65_507,
        msgFlags: <<0x04>>,
        msgSecurityModel: 3,
        msgSecurityParameters: "",
        contextEngineID: "",
        contextName: "",
        pdu: %{
          type: :get_request,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: []
        }
      }

      user = %{
        auth_protocol: :md5,
        auth_key: "authentication_key",
        priv_protocol: :des,
        priv_key: "privacy_key"
      }

      # Should delegate to V3Encoder (may fail if V3Encoder requires specific fields)
      result = Encoder.encode_message(message, user)
      assert is_tuple(result) and elem(result, 0) in [:ok, :error]
    end

    test "falls back to regular encoding for v1/v2c with user parameter" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_request,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: []
        }
      }

      user = %{auth_protocol: :md5}

      assert {:ok, encoded} = Encoder.encode_message(message, user)
      assert is_binary(encoded)
    end

    test "ignores user parameter for non-v3 messages" do
      message = %{
        version: 0,
        community: "public",
        pdu: %{
          type: :get_request,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: []
        }
      }

      assert {:ok, encoded1} = Encoder.encode_message(message, nil)
      assert {:ok, encoded2} = Encoder.encode_message(message, %{some: "user"})
      assert encoded1 == encoded2
    end
  end

  describe "encode_pdu/1" do
    test "encodes get_request PDU" do
      pdu = %{
        type: :get_request,
        request_id: 123,
        error_status: 0,
        error_index: 0,
        varbinds: [{[1, 3, 6, 1], :null, nil}]
      }

      assert {:ok, encoded} = Encoder.encode_pdu(pdu)
      assert is_binary(encoded)
      # Should start with get_request tag (0xA0)
      assert <<0xA0, _::binary>> = encoded
    end

    test "encodes get_next_request PDU" do
      pdu = %{
        type: :get_next_request,
        request_id: 456,
        error_status: 0,
        error_index: 0,
        varbinds: [{[1, 3, 6], :null, nil}]
      }

      assert {:ok, encoded} = Encoder.encode_pdu(pdu)
      assert is_binary(encoded)
      # Should start with getnext_request tag (0xA1)
      assert <<0xA1, _::binary>> = encoded
    end

    test "encodes get_response PDU" do
      pdu = %{
        type: :get_response,
        request_id: 789,
        error_status: 0,
        error_index: 0,
        varbinds: [{[1, 3, 6, 1, 2, 1, 1, 1, 0], :octet_string, "System description"}]
      }

      assert {:ok, encoded} = Encoder.encode_pdu(pdu)
      assert is_binary(encoded)
      # Should start with get_response tag (0xA2)
      assert <<0xA2, _::binary>> = encoded
    end

    test "encodes set_request PDU" do
      pdu = %{
        type: :set_request,
        request_id: 111,
        error_status: 0,
        error_index: 0,
        varbinds: [{[1, 3, 6, 1, 2, 1, 1, 5, 0], :octet_string, "NewName"}]
      }

      assert {:ok, encoded} = Encoder.encode_pdu(pdu)
      assert is_binary(encoded)
      # Should start with set_request tag (0xA3)
      assert <<0xA3, _::binary>> = encoded
    end

    test "encodes get_bulk_request PDU" do
      pdu = %{
        type: :get_bulk_request,
        request_id: 999,
        non_repeaters: 0,
        max_repetitions: 10,
        varbinds: [{[1, 3, 6, 1, 2, 1, 2, 2], :null, nil}]
      }

      assert {:ok, encoded} = Encoder.encode_pdu(pdu)
      assert is_binary(encoded)
      # Should start with getbulk_request tag (0xA5)
      assert <<0xA5, _::binary>> = encoded
    end

    test "returns error for unsupported PDU type" do
      pdu = %{
        type: :unsupported_type,
        request_id: 1,
        error_status: 0,
        error_index: 0,
        varbinds: []
      }

      assert {:error, :unsupported_pdu_type} = Encoder.encode_pdu(pdu)
    end

    test "encodes PDU with empty varbinds" do
      pdu = %{
        type: :get_request,
        request_id: 1,
        error_status: 0,
        error_index: 0,
        varbinds: []
      }

      assert {:ok, encoded} = Encoder.encode_pdu(pdu)
      assert is_binary(encoded)
    end

    test "encodes PDU with multiple varbinds" do
      pdu = %{
        type: :get_request,
        request_id: 1,
        error_status: 0,
        error_index: 0,
        varbinds: [
          {[1, 3, 6, 1, 2, 1, 1, 1, 0], :null, nil},
          {[1, 3, 6, 1, 2, 1, 1, 3, 0], :null, nil},
          {[1, 3, 6, 1, 2, 1, 1, 5, 0], :null, nil}
        ]
      }

      assert {:ok, encoded} = Encoder.encode_pdu(pdu)
      assert is_binary(encoded)
    end

    test "encodes PDU with large request_id" do
      pdu = %{
        type: :get_request,
        request_id: 2_147_483_647,
        error_status: 0,
        error_index: 0,
        varbinds: []
      }

      assert {:ok, encoded} = Encoder.encode_pdu(pdu)
      assert is_binary(encoded)
    end
  end

  describe "encode/1 and encode_snmp_packet/1" do
    test "encode/1 is an alias for encode_message/1" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_request,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: []
        }
      }

      assert {:ok, encoded1} = Encoder.encode(message)
      assert {:ok, encoded2} = Encoder.encode_message(message)
      assert encoded1 == encoded2
    end

    test "encode_snmp_packet/1 is an alias for encode/1" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_request,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: []
        }
      }

      assert {:ok, encoded1} = Encoder.encode_snmp_packet(message)
      assert {:ok, encoded2} = Encoder.encode(message)
      assert encoded1 == encoded2
    end
  end

  describe "varbind encoding" do
    test "encodes varbind with explicit null type" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_request,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 1, 1, 0], :null, nil}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes varbind with integer value" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 1, 7, 0], :integer, 72}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes varbind with octet_string value" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 1, 1, 0], :octet_string, "System description"}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes varbind with counter32 value" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 2, 1, 10, 1], :counter32, 1_234_567}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes varbind with gauge32 value" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 2, 1, 5, 1], :gauge32, 1_000_000}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes varbind with timeticks value" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 1, 3, 0], :timeticks, 12_345_678}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes varbind with counter64 value" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 31, 1, 1, 1, 6, 1], :counter64, 9_876_543_210}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes varbind with ip_address value" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 4, 20, 1, 1, 1], :ip_address, <<192, 168, 1, 1>>}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes varbind with opaque value" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 4, 1, 9, 2, 1, 57, 0], :opaque, <<0x9F, 0x78, 0x04, 0x41, 0x70, 0x00, 0x00>>}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes varbind with object_identifier value (list)" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 1, 2, 0], :object_identifier, [1, 3, 6, 1, 4, 1]}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes varbind with object_identifier value (string)" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 1, 2, 0], :object_identifier, "1.3.6.1.4.1"}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end
  end

  describe "auto type varbind encoding" do
    test "encodes varbind with auto type and integer value" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 1, 7, 0], :auto, 72}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes varbind with auto type and string value" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 1, 5, 0], :auto, "router1"}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes varbind with auto type and OID list" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 1, 2, 0], :auto, [1, 3, 6, 1, 4, 1, 9]}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes varbind with auto type and OID string" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 1, 2, 0], :auto, "1.3.6.1.4.1.9"}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes varbind with auto type and null value" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_request,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 1, 1, 0], :auto, nil}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes varbind with auto type and :null atom" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_request,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 1, 1, 0], :auto, :null}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes varbind with auto type and typed tuple" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [
            {[1, 3, 6, 1, 2, 1, 2, 1, 10, 1], :auto, {:counter32, 1_234_567}},
            {[1, 3, 6, 1, 2, 1, 2, 1, 5, 1], :auto, {:gauge32, 1_000_000}},
            {[1, 3, 6, 1, 2, 1, 1, 3, 0], :auto, {:timeticks, 12_345}},
            {[1, 3, 6, 1, 2, 1, 31, 1, 1, 1, 6, 1], :auto, {:counter64, 9_876_543_210}}
          ]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes varbind with auto type and exception values" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [
            {[1, 3, 6, 1, 2, 1, 99, 1], :auto, {:no_such_object, nil}},
            {[1, 3, 6, 1, 2, 1, 99, 2], :auto, {:no_such_instance, nil}},
            {[1, 3, 6, 1, 2, 1, 99, 3], :auto, {:end_of_mib_view, nil}}
          ]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end
  end

  describe "exception value encoding" do
    test "encodes no_such_object exception" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 99, 1], :no_such_object, nil}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes no_such_instance exception" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 99, 2], :no_such_instance, nil}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes end_of_mib_view exception" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 99, 3], :end_of_mib_view, nil}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end
  end

  describe "integer encoding edge cases" do
    test "encodes zero integer" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 0,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1], :integer, 0}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes small positive integer (<128)" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1], :integer, 42}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes large positive integer" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1], :integer, 2_147_483_647}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes negative integer" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1], :integer, -100}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes large negative integer" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1], :integer, -2_147_483_648}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end
  end

  describe "counter32/gauge32/timeticks encoding" do
    test "encodes counter32 min value" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1], :counter32, 0}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes counter32 max value" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1], :counter32, 4_294_967_295}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes gauge32 min value" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1], :gauge32, 0}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes gauge32 max value" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1], :gauge32, 4_294_967_295}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes timeticks min value" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 1, 3, 0], :timeticks, 0}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes timeticks max value" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 1, 3, 0], :timeticks, 4_294_967_295}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end
  end

  describe "counter64 encoding" do
    test "encodes counter64 min value" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 31, 1, 1, 1, 6, 1], :counter64, 0}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes counter64 large value" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 31, 1, 1, 1, 6, 1], :counter64, 9_999_999_999_999}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes counter64 max value" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 31, 1, 1, 1, 6, 1], :counter64, 18_446_744_073_709_551_615}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end
  end

  describe "string encoding" do
    test "encodes empty string" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 1, 1, 0], :octet_string, ""}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes short string" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 1, 1, 0], :octet_string, "test"}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes long string" do
      long_string = String.duplicate("A", 256)

      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 1, 1, 0], :octet_string, long_string}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes string with special characters" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 1, 1, 0], :octet_string, "Test\nString\twith\rspecial"}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes binary string" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 1, 1, 0], :octet_string, <<0x00, 0x01, 0x02, 0xFF>>}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end
  end

  describe "OID encoding" do
    test "encodes simple OID" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_request,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1], :null, nil}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes OID with large subidentifiers" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_request,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 1000], :null, nil}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes OID with very large subidentifiers" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_request,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 4, 1, 9, 9, 1, 1, 1, 1, 1_000_000], :null, nil}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes long OID" do
      long_oid = [1, 3, 6, 1, 2, 1, 2, 2, 1, 1] ++ Enum.to_list(1..50)

      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_request,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{long_oid, :null, nil}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes minimum OID (two components)" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_request,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[0, 0], :null, nil}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes OID starting with 2" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_request,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[2, 5, 29, 17], :null, nil}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end
  end

  describe "bulk request encoding" do
    test "encodes bulk request with non_repeaters" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_bulk_request,
          request_id: 1,
          non_repeaters: 1,
          max_repetitions: 10,
          varbinds: [
            {[1, 3, 6, 1, 2, 1, 1, 1, 0], :null, nil},
            {[1, 3, 6, 1, 2, 1, 2, 2, 1, 10], :null, nil}
          ]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes bulk request with zero non_repeaters" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_bulk_request,
          request_id: 1,
          non_repeaters: 0,
          max_repetitions: 25,
          varbinds: [{[1, 3, 6, 1, 2, 1, 2, 2, 1, 10], :null, nil}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes bulk request with large max_repetitions" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_bulk_request,
          request_id: 1,
          non_repeaters: 0,
          max_repetitions: 1000,
          varbinds: [{[1, 3, 6, 1, 2, 1, 2, 2], :null, nil}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end
  end

  describe "error handling" do
    test "handles encoding errors gracefully" do
      # Invalid varbind format should be caught
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_request,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: ["invalid_varbind"]
        }
      }

      assert {:error, _reason} = Encoder.encode_message(message)
    end

    test "handles missing PDU fields" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_request
          # Missing required fields
        }
      }

      assert {:error, _reason} = Encoder.encode_message(message)
    end
  end

  describe "PDU with error status" do
    test "encodes PDU with non-zero error_status" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 2,
          error_index: 1,
          varbinds: [{[1, 3, 6, 1, 2, 1, 1, 1, 0], :null, nil}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes PDU with various error statuses" do
      for error_status <- 0..5 do
        message = %{
          version: 1,
          community: "public",
          pdu: %{
            type: :get_response,
            request_id: 1,
            error_status: error_status,
            error_index: 0,
            varbinds: []
          }
        }

        assert {:ok, encoded} = Encoder.encode_message(message)
        assert is_binary(encoded)
      end
    end
  end

  describe "community string variations" do
    test "encodes message with short community" do
      message = %{
        version: 1,
        community: "p",
        pdu: %{
          type: :get_request,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: []
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes message with long community" do
      long_community = String.duplicate("community", 20)

      message = %{
        version: 1,
        community: long_community,
        pdu: %{
          type: :get_request,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: []
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes message with special characters in community" do
      message = %{
        version: 1,
        community: "test@community#123",
        pdu: %{
          type: :get_request,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: []
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end
  end

  describe "varbind format variations" do
    test "encodes varbind as two-tuple (implicit auto type)" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_request,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 1, 1, 0], nil}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes varbind as two-tuple with value" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1, 2, 1, 1, 1, 0], "Test"}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end
  end

  describe "length encoding edge cases" do
    test "encodes value with length < 128 (short form)" do
      # Short string (< 128 bytes)
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1], :octet_string, "Short"}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes value with length 128-255 (long form 1 byte)" do
      # String of 150 bytes
      string = String.duplicate("A", 150)

      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1], :octet_string, string}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes value with length 256-65535 (long form 2 bytes)" do
      # String of 300 bytes
      string = String.duplicate("B", 300)

      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 1,
          error_status: 0,
          error_index: 0,
          varbinds: [{[1, 3, 6, 1], :octet_string, string}]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end
  end

  describe "comprehensive integration tests" do
    test "encodes complete SNMP GET request" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_request,
          request_id: 12_345,
          error_status: 0,
          error_index: 0,
          varbinds: [
            {[1, 3, 6, 1, 2, 1, 1, 1, 0], :null, nil},
            {[1, 3, 6, 1, 2, 1, 1, 3, 0], :null, nil},
            {[1, 3, 6, 1, 2, 1, 1, 5, 0], :null, nil}
          ]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
      assert byte_size(encoded) > 0
    end

    test "encodes complete SNMP GET response with multiple types" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_response,
          request_id: 12_345,
          error_status: 0,
          error_index: 0,
          varbinds: [
            {[1, 3, 6, 1, 2, 1, 1, 1, 0], :octet_string, "Linux router 4.19.0"},
            {[1, 3, 6, 1, 2, 1, 1, 3, 0], :timeticks, 123_456_789},
            {[1, 3, 6, 1, 2, 1, 1, 5, 0], :octet_string, "router1.example.com"},
            {[1, 3, 6, 1, 2, 1, 1, 7, 0], :integer, 72},
            {[1, 3, 6, 1, 2, 1, 2, 1, 10, 1], :counter32, 987_654_321}
          ]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
      assert byte_size(encoded) > 0
    end

    test "encodes SNMP SET request" do
      message = %{
        version: 1,
        community: "private",
        pdu: %{
          type: :set_request,
          request_id: 9999,
          error_status: 0,
          error_index: 0,
          varbinds: [
            {[1, 3, 6, 1, 2, 1, 1, 5, 0], :octet_string, "NewHostName"},
            {[1, 3, 6, 1, 2, 1, 1, 6, 0], :octet_string, "Data Center A"}
          ]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes SNMP GETNEXT request" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_next_request,
          request_id: 5555,
          error_status: 0,
          error_index: 0,
          varbinds: [
            {[1, 3, 6, 1, 2, 1, 2, 2, 1, 1], :null, nil}
          ]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end

    test "encodes SNMP GETBULK request" do
      message = %{
        version: 1,
        community: "public",
        pdu: %{
          type: :get_bulk_request,
          request_id: 7777,
          non_repeaters: 1,
          max_repetitions: 10,
          varbinds: [
            {[1, 3, 6, 1, 2, 1, 1, 1, 0], :null, nil},
            {[1, 3, 6, 1, 2, 1, 2, 2, 1, 2], :null, nil}
          ]
        }
      }

      assert {:ok, encoded} = Encoder.encode_message(message)
      assert is_binary(encoded)
    end
  end
end
