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

  alias SnmpKit.SnmpLib.ASN1

  @moduletag :unit
  @moduletag :protocol
  @moduletag :phase_2

  describe "Integer encoding and decoding" do
    test "encodes and decodes positive integers" do
      {:ok, encoded} = ASN1.encode_integer(42)
      {:ok, {decoded, <<>>}} = ASN1.decode_integer(encoded)
      assert decoded == 42
    end

    test "encodes and decodes zero" do
      {:ok, encoded} = ASN1.encode_integer(0)
      {:ok, {decoded, <<>>}} = ASN1.decode_integer(encoded)
      assert decoded == 0
    end

    test "encodes and decodes negative integers" do
      {:ok, encoded} = ASN1.encode_integer(-1)
      {:ok, {decoded, <<>>}} = ASN1.decode_integer(encoded)
      assert decoded == -1

      {:ok, encoded} = ASN1.encode_integer(-128)
      {:ok, {decoded, <<>>}} = ASN1.decode_integer(encoded)
      assert decoded == -128
    end

    test "encodes and decodes large integers" do
      large_value = 123_456_789
      {:ok, encoded} = ASN1.encode_integer(large_value)
      {:ok, {decoded, <<>>}} = ASN1.decode_integer(encoded)
      assert decoded == large_value
    end

    test "decodes integer with remaining data" do
      {:ok, encoded} = ASN1.encode_integer(42)
      test_data = encoded <> <<99, 100, 101>>
      {:ok, {decoded, remaining}} = ASN1.decode_integer(test_data)
      assert decoded == 42
      assert remaining == <<99, 100, 101>>
    end

    test "rejects invalid integer tags" do
      assert {:error, :invalid_tag} = ASN1.decode_integer(<<0x04, 0x01, 0x42>>)
      assert {:error, :invalid_tag} = ASN1.decode_integer(<<0x05, 0x00>>)
    end
  end

  describe "OCTET STRING encoding and decoding" do
    test "encodes and decodes octet strings" do
      test_string = "Hello, World!"
      {:ok, encoded} = ASN1.encode_octet_string(test_string)
      {:ok, {decoded, <<>>}} = ASN1.decode_octet_string(encoded)
      assert decoded == test_string
    end

    test "encodes and decodes empty octet strings" do
      {:ok, encoded} = ASN1.encode_octet_string("")
      {:ok, {decoded, <<>>}} = ASN1.decode_octet_string(encoded)
      assert decoded == ""
    end

    test "encodes and decodes binary data" do
      binary_data = <<1, 2, 3, 255, 0, 42>>
      {:ok, encoded} = ASN1.encode_octet_string(binary_data)
      {:ok, {decoded, <<>>}} = ASN1.decode_octet_string(encoded)
      assert decoded == binary_data
    end

    test "decodes octet string with remaining data" do
      {:ok, encoded} = ASN1.encode_octet_string("test")
      test_data = encoded <> <<99, 100>>
      {:ok, {decoded, remaining}} = ASN1.decode_octet_string(test_data)
      assert decoded == "test"
      assert remaining == <<99, 100>>
    end

    test "rejects invalid octet string tags" do
      assert {:error, :invalid_tag} = ASN1.decode_octet_string(<<0x02, 0x01, 0x42>>)
    end
  end

  describe "NULL encoding and decoding" do
    test "encodes and decodes null values" do
      {:ok, encoded} = ASN1.encode_null()
      assert encoded == <<0x05, 0x00>>
      {:ok, {decoded, <<>>}} = ASN1.decode_null(encoded)
      assert decoded == :null
    end

    test "decodes null with remaining data" do
      test_data = <<0x05, 0x00, 42, 43>>
      {:ok, {decoded, remaining}} = ASN1.decode_null(test_data)
      assert decoded == :null
      assert remaining == <<42, 43>>
    end

    test "rejects null with invalid length" do
      assert {:error, :invalid_null_length} = ASN1.decode_null(<<0x05, 0x01, 0x00>>)
    end

    test "rejects invalid null tags" do
      assert {:error, :invalid_tag} = ASN1.decode_null(<<0x02, 0x00>>)
    end
  end

  describe "OBJECT IDENTIFIER encoding and decoding" do
    test "encodes and decodes simple OIDs" do
      oid = [1, 3, 6, 1, 2, 1, 1, 1, 0]
      {:ok, encoded} = ASN1.encode_oid(oid)
      {:ok, {decoded, <<>>}} = ASN1.decode_oid(encoded)
      assert decoded == oid
    end

    test "encodes and decodes short OIDs" do
      oid = [1, 3]
      {:ok, encoded} = ASN1.encode_oid(oid)
      {:ok, {decoded, <<>>}} = ASN1.decode_oid(encoded)
      assert decoded == oid
    end

    test "encodes and decodes OIDs with large components" do
      oid = [1, 3, 6, 1, 4, 1, 200, 1]
      {:ok, encoded} = ASN1.encode_oid(oid)
      {:ok, {decoded, <<>>}} = ASN1.decode_oid(encoded)
      assert decoded == oid
    end

    test "decodes OID with remaining data" do
      oid = [1, 3, 6, 1]
      {:ok, encoded} = ASN1.encode_oid(oid)
      test_data = encoded <> <<99, 100>>
      {:ok, {decoded, remaining}} = ASN1.decode_oid(test_data)
      assert decoded == oid
      assert remaining == <<99, 100>>
    end

    test "rejects invalid OIDs" do
      assert {:error, :invalid_oid} = ASN1.encode_oid([])
      # Single-component OIDs are valid
      assert {:ok, _} = ASN1.encode_oid([1])
      assert {:error, :invalid_oid} = ASN1.encode_oid(:not_a_list)
    end

    test "rejects invalid OID tags" do
      assert {:error, :invalid_tag} = ASN1.decode_oid(<<0x02, 0x01, 0x42>>)
    end
  end

  describe "SEQUENCE encoding and decoding" do
    test "encodes and decodes sequences" do
      # Create sequence content: INTEGER 42 + OCTET STRING "test"
      {:ok, int_encoded} = ASN1.encode_integer(42)
      {:ok, str_encoded} = ASN1.encode_octet_string("test")
      content = int_encoded <> str_encoded

      {:ok, sequence_encoded} = ASN1.encode_sequence(content)
      {:ok, {decoded_content, <<>>}} = ASN1.decode_sequence(sequence_encoded)
      assert decoded_content == content
    end

    test "encodes and decodes empty sequences" do
      {:ok, encoded} = ASN1.encode_sequence(<<>>)
      {:ok, {decoded, <<>>}} = ASN1.decode_sequence(encoded)
      assert decoded == <<>>
    end

    test "decodes sequence with remaining data" do
      {:ok, encoded} = ASN1.encode_sequence(<<1, 2, 3>>)
      test_data = encoded <> <<99, 100>>
      {:ok, {decoded, remaining}} = ASN1.decode_sequence(test_data)
      assert decoded == <<1, 2, 3>>
      assert remaining == <<99, 100>>
    end

    test "rejects invalid sequence tags" do
      assert {:error, :invalid_tag} = ASN1.decode_sequence(<<0x02, 0x01, 0x42>>)
    end
  end

  describe "Custom TLV encoding" do
    test "encodes custom TLV structures" do
      custom_tag = 0xA0
      content = "custom_content"
      {:ok, encoded} = ASN1.encode_custom_tlv(custom_tag, content)

      # Should start with our custom tag
      assert <<^custom_tag, _rest::binary>> = encoded
    end

    test "encodes TLV with different tags" do
      {:ok, encoded1} = ASN1.encode_custom_tlv(0x80, "content1")
      {:ok, encoded2} = ASN1.encode_custom_tlv(0x81, "content2")

      assert <<0x80, _::binary>> = encoded1
      assert <<0x81, _::binary>> = encoded2
    end
  end

  describe "Generic TLV decoding" do
    test "decodes any TLV structure" do
      {:ok, encoded} = ASN1.encode_integer(42)
      {:ok, {tag, content, <<>>}} = ASN1.decode_tlv(encoded)

      # INTEGER tag
      assert tag == 0x02
      assert content == <<42>>
    end

    test "decodes TLV with remaining data" do
      {:ok, encoded} = ASN1.encode_octet_string("test")
      test_data = encoded <> <<99, 100>>
      {:ok, {tag, content, remaining}} = ASN1.decode_tlv(test_data)

      # OCTET STRING tag
      assert tag == 0x04
      assert content == "test"
      assert remaining == <<99, 100>>
    end

    test "rejects insufficient data" do
      assert {:error, :insufficient_data} = ASN1.decode_tlv(<<>>)
    end
  end

  describe "Length encoding and decoding" do
    test "encodes and decodes short form lengths" do
      for length <- [0, 1, 42, 127] do
        encoded = ASN1.encode_length(length)
        {:ok, {decoded, <<>>}} = ASN1.decode_length(encoded)
        assert decoded == length
      end
    end

    test "encodes and decodes long form lengths" do
      test_cases = [128, 200, 300, 1000, 65_535, 100_000]

      for length <- test_cases do
        encoded = ASN1.encode_length(length)
        {:ok, {decoded, <<>>}} = ASN1.decode_length(encoded)
        assert decoded == length
      end
    end

    test "decodes length with remaining data" do
      encoded = ASN1.encode_length(42)
      test_data = encoded <> <<99, 100>>
      {:ok, {decoded, remaining}} = ASN1.decode_length(test_data)
      assert decoded == 42
      assert remaining == <<99, 100>>
    end

    test "rejects indefinite length" do
      assert {:error, :indefinite_length_not_supported} = ASN1.decode_length(<<0x80>>)
    end

    test "rejects overly long length encoding" do
      # 5 octets for length is too much
      assert {:error, :length_too_large} = ASN1.decode_length(<<0x85, 1, 2, 3, 4, 5>>)
    end

    test "rejects insufficient length bytes" do
      assert {:error, :insufficient_length_bytes} = ASN1.decode_length(<<0x82, 0x01>>)
    end
  end

  describe "Tag parsing" do
    test "parses universal class tags" do
      # INTEGER
      info = ASN1.parse_tag(0x02)
      assert info.class == :universal
      assert info.constructed == false
      assert info.tag_number == 2
      assert info.original_byte == 0x02
    end

    test "parses constructed tags" do
      # SEQUENCE
      info = ASN1.parse_tag(0x30)
      assert info.class == :universal
      assert info.constructed == true
      assert info.tag_number == 16
    end

    test "parses application class tags" do
      # Application, primitive, tag 1
      info = ASN1.parse_tag(0x41)
      assert info.class == :application
      assert info.constructed == false
      assert info.tag_number == 1
    end

    test "parses context class tags" do
      # Context, primitive, tag 0
      info = ASN1.parse_tag(0x80)
      assert info.class == :context
      assert info.constructed == false
      assert info.tag_number == 0
    end

    test "parses private class tags" do
      # Private, primitive, tag 0
      info = ASN1.parse_tag(0xC0)
      assert info.class == :private
      assert info.constructed == false
      assert info.tag_number == 0
    end
  end

  describe "BER structure validation" do
    test "validates simple valid structures" do
      {:ok, encoded} = ASN1.encode_integer(42)
      assert :ok = ASN1.validate_ber_structure(encoded)
    end

    test "validates sequences of structures" do
      {:ok, int_encoded} = ASN1.encode_integer(42)
      {:ok, str_encoded} = ASN1.encode_octet_string("test")
      combined = int_encoded <> str_encoded

      assert :ok = ASN1.validate_ber_structure(combined)
    end

    test "validates nested sequences" do
      # Sequence with INTEGER 42
      {:ok, inner_seq} = ASN1.encode_sequence(<<0x02, 0x01, 0x42>>)
      {:ok, outer_seq} = ASN1.encode_sequence(inner_seq)

      assert :ok = ASN1.validate_ber_structure(outer_seq)
    end

    test "rejects malformed structures" do
      # Invalid length - claims 10 bytes but only has 2
      malformed = <<0x02, 0x0A, 0x42, 0x43>>
      assert {:error, :insufficient_content} = ASN1.validate_ber_structure(malformed)
    end

    test "validates empty data" do
      assert :ok = ASN1.validate_ber_structure(<<>>)
    end
  end

  describe "BER length calculation" do
    test "calculates length for simple structures" do
      {:ok, encoded} = ASN1.encode_integer(42)
      {:ok, total_length} = ASN1.calculate_ber_length(encoded)
      assert total_length == byte_size(encoded)
    end

    test "calculates length for different structure sizes" do
      test_cases = [
        42,
        123_456,
        "Hello, World!",
        String.duplicate("A", 200)
      ]

      for test_value <- test_cases do
        {:ok, encoded} =
          case test_value do
            value when is_integer(value) -> ASN1.encode_integer(value)
            value when is_binary(value) -> ASN1.encode_octet_string(value)
          end

        {:ok, calculated_length} = ASN1.calculate_ber_length(encoded)
        actual_length = byte_size(encoded)
        assert calculated_length == actual_length
      end
    end

    test "rejects insufficient data for length calculation" do
      assert {:error, :insufficient_data} = ASN1.calculate_ber_length(<<>>)
      assert {:error, :insufficient_data} = ASN1.calculate_ber_length(<<0x02>>)
    end
  end

  describe "Round-trip encoding/decoding" do
    test "integer round-trips correctly" do
      test_values = [0, 1, -1, 42, -42, 127, -128, 255, -256, 32_767, -32_768]

      for value <- test_values do
        {:ok, encoded} = ASN1.encode_integer(value)
        {:ok, {decoded, <<>>}} = ASN1.decode_integer(encoded)
        assert decoded == value, "Failed round-trip for integer #{value}"
      end
    end

    test "octet string round-trips correctly" do
      test_values = [
        "",
        "Hello",
        "Hello, World!",
        <<0, 1, 2, 255>>,
        String.duplicate("A", 100)
      ]

      for value <- test_values do
        {:ok, encoded} = ASN1.encode_octet_string(value)
        {:ok, {decoded, <<>>}} = ASN1.decode_octet_string(encoded)
        assert decoded == value
      end
    end

    test "OID round-trips correctly" do
      test_oids = [
        [1, 3],
        [1, 3, 6, 1, 2, 1],
        [1, 3, 6, 1, 2, 1, 1, 1, 0],
        [1, 3, 6, 1, 4, 1, 200, 1, 2, 3]
      ]

      for oid <- test_oids do
        {:ok, encoded} = ASN1.encode_oid(oid)
        {:ok, {decoded, <<>>}} = ASN1.decode_oid(encoded)
        assert decoded == oid
      end
    end
  end

  describe "Error handling and edge cases" do
    test "handles encoding failures gracefully" do
      # These should not crash, but return errors
      assert {:error, :invalid_oid} = ASN1.encode_oid([])
      assert {:error, :invalid_oid} = ASN1.encode_oid(:not_a_list)
    end

    test "handles decoding failures gracefully" do
      # Truncated data
      assert {:error, :insufficient_data} = ASN1.decode_integer(<<0x02>>)
      assert {:error, :insufficient_content} = ASN1.decode_integer(<<0x02, 0x05, 0x42>>)
    end

    test "handles empty input data" do
      assert {:error, :invalid_tag} = ASN1.decode_integer(<<>>)
      assert {:error, :invalid_tag} = ASN1.decode_octet_string(<<>>)
      assert {:error, :invalid_tag} = ASN1.decode_oid(<<>>)
      assert {:error, :invalid_tag} = ASN1.decode_sequence(<<>>)
    end

    test "handles concurrent operations" do
      # Test thread safety
      tasks =
        for i <- 1..20 do
          Task.async(fn ->
            value = rem(i, 1000)
            {:ok, encoded} = ASN1.encode_integer(value)
            {:ok, {decoded, <<>>}} = ASN1.decode_integer(encoded)
            {i, value, decoded}
          end)
        end

      results = Task.await_many(tasks, 1000)

      # Verify all operations completed successfully
      assert length(results) == 20

      for {i, original, decoded} <- results do
        expected = rem(i, 1000)
        assert original == expected
        assert decoded == expected
      end
    end
  end

  describe "Performance and large data handling" do
    @tag :performance
    test "handles large integers efficiently" do
      large_values = [
        123_456_789,
        -123_456_789,
        2_147_483_647,
        -2_147_483_648
      ]

      for value <- large_values do
        start_time = System.monotonic_time(:microsecond)
        {:ok, encoded} = ASN1.encode_integer(value)
        {:ok, {decoded, <<>>}} = ASN1.decode_integer(encoded)
        end_time = System.monotonic_time(:microsecond)

        assert decoded == value
        # Should complete in reasonable time (< 1ms)
        assert end_time - start_time < 1000
      end
    end

    test "handles large octet strings efficiently" do
      large_string = String.duplicate("Hello, World! ", 100)

      start_time = System.monotonic_time(:microsecond)
      {:ok, encoded} = ASN1.encode_octet_string(large_string)
      {:ok, {decoded, <<>>}} = ASN1.decode_octet_string(encoded)
      end_time = System.monotonic_time(:microsecond)

      assert decoded == large_string
      # Should complete in reasonable time (< 100ms, adjusted for CI/busy systems)
      assert end_time - start_time < 100_000
    end

    test "handles complex nested structures" do
      # Create a sequence containing multiple nested sequences
      # INTEGER 42
      {:ok, inner1} = ASN1.encode_sequence(<<0x02, 0x01, 0x42>>)
      # OCTET STRING "test"
      {:ok, inner2} = ASN1.encode_sequence(<<0x04, 0x04, "test"::binary>>)
      {:ok, outer} = ASN1.encode_sequence(inner1 <> inner2)

      # Should be able to decode the outer structure
      {:ok, {content, <<>>}} = ASN1.decode_sequence(outer)
      assert content == inner1 <> inner2

      # Should validate correctly
      assert :ok = ASN1.validate_ber_structure(outer)
    end
  end
end
