defmodule SnmpKit.OidArchitectureValidationTest do
  @moduledoc """
  Tests to validate that the OID handling architecture follows the established principles:

  1. All internal OID handling uses lists of integers [1,3,6,1,2,1,1,1,0]
  2. All external APIs accept both string "1.3.6.1.2.1.1.1.0" and list [1,3,6,1,2,1,1,1,0] formats
  3. Conversion happens only at API boundaries
  4. No unnecessary string/list conversions in internal operations
  """

  use ExUnit.Case

  alias SnmpKit.SnmpLib.OID
  alias SnmpKit.SnmpMgr.Core
  alias SnmpKit.SnmpMgr.Format
  alias SnmpKit.SnmpMgr.MIB

  doctest SnmpKit

  describe "Core API Entry Points" do
    test "parse_oid function always returns lists" do
      # Test string input
      assert {:ok, [1, 3, 6, 1, 2, 1, 1, 1, 0]} =
               Core.parse_oid("1.3.6.1.2.1.1.1.0")

      assert {:ok, [1, 3, 6, 1, 2, 1, 1, 1, 0]} =
               Core.parse_oid(".1.3.6.1.2.1.1.1.0")

      # Test list input - should validate and return same list
      assert {:ok, [1, 3, 6, 1, 2, 1, 1, 1, 0]} =
               Core.parse_oid([1, 3, 6, 1, 2, 1, 1, 1, 0])

      # Test empty cases get proper fallbacks
      assert {:ok, [1, 3]} = Core.parse_oid("")
      assert {:ok, [1, 3]} = Core.parse_oid([])

      # Test invalid cases
      assert {:error, :invalid_oid_input} = Core.parse_oid(123)
      assert {:error, :invalid_oid_input} = Core.parse_oid(nil)
    end

    test "API functions accept both string and list OID formats" do
      # These would normally make network calls, but we're testing the input validation
      # The functions should not crash on valid OID formats

      string_oid = "1.3.6.1.2.1.1.1.0"
      list_oid = [1, 3, 6, 1, 2, 1, 1, 1, 0]

      # Test that both formats are accepted (they will fail with network errors, but not format errors)
      result1 = SnmpKit.SnmpMgr.get("invalid.host", string_oid, timeout: 100)
      result2 = SnmpKit.SnmpMgr.get("invalid.host", list_oid, timeout: 100)

      # Both should fail with network errors, not format errors
      assert match?({:error, _}, result1)
      assert match?({:error, _}, result2)

      # Neither should fail with format-specific errors
      refute match?({:error, :invalid_oid_format}, result1)
      refute match?({:error, :invalid_oid_format}, result2)
      refute match?({:error, :invalid_oid_input}, result1)
      refute match?({:error, :invalid_oid_input}, result2)
    end
  end

  describe "Internal Consistency" do
    test "OID validation uses list format internally" do
      # Test the OID validation functions work with lists
      assert :ok = OID.valid_oid?([1, 3, 6, 1, 2, 1, 1, 1, 0])
      assert {:error, :empty_oid} = OID.valid_oid?([])
      assert {:error, :invalid_component} = OID.valid_oid?([1, -1, 3])
      assert {:error, :invalid_input} = OID.valid_oid?("string")
    end

    test "OID normalization returns lists consistently" do
      # Test the normalize function in OID module
      assert {:ok, [1, 3, 6, 1, 2, 1, 1, 1, 0]} =
               OID.normalize("1.3.6.1.2.1.1.1.0")

      assert {:ok, [1, 3, 6, 1, 2, 1, 1, 1, 0]} =
               OID.normalize([1, 3, 6, 1, 2, 1, 1, 1, 0])

      # Empty cases return error for OID module (but get fallback in parse_oid)
      assert {:error, :empty_oid} = OID.normalize("")
      assert {:error, :empty_oid} = OID.normalize([])
    end

    test "bulk operations maintain list format internally" do
      # Test that bulk resolve_oid function returns lists
      test_cases = [
        {"1.3.6.1.2.1.1.1.0", [1, 3, 6, 1, 2, 1, 1, 1, 0]},
        {[1, 3, 6, 1, 2, 1, 1, 1, 0], [1, 3, 6, 1, 2, 1, 1, 1, 0]},
        {"", [1, 3]},
        {[], [1, 3]}
      ]

      Enum.each(test_cases, fn {input, expected} ->
        # Access the private resolve_oid function through a test helper pattern
        # This validates internal consistency without exposing private APIs
        case Core.parse_oid(input) do
          {:ok, result} ->
            assert result == expected,
                   "resolve_oid(#{inspect(input)}) should return #{inspect(expected)}, got #{inspect(result)}"

          error ->
            flunk("resolve_oid(#{inspect(input)}) returned error: #{inspect(error)}")
        end
      end)
    end
  end

  describe "API Boundaries and Conversion" do
    test "string-to-list conversion happens at entry points" do
      # Test OID string parsing
      assert {:ok, [1, 3, 6, 1, 2, 1, 1, 1, 0]} =
               OID.string_to_list("1.3.6.1.2.1.1.1.0")

      assert {:ok, [1, 3, 6, 1, 2, 1, 1, 1, 0]} =
               OID.string_to_list(".1.3.6.1.2.1.1.1.0")

      # Test edge cases
      assert {:error, :empty_oid} = OID.string_to_list("")
      assert {:error, :invalid_oid_string} = OID.string_to_list("1.3.6.x")
      assert {:error, :invalid_input} = OID.string_to_list(123)
    end

    test "list-to-string conversion for output only" do
      # Test OID list to string conversion
      assert {:ok, "1.3.6.1.2.1.1.1.0"} =
               OID.list_to_string([1, 3, 6, 1, 2, 1, 1, 1, 0])

      # Test edge cases
      assert {:error, :empty_oid} = OID.list_to_string([])
      assert {:error, :invalid_component} = OID.list_to_string([1, -1, 3])
      assert {:error, :invalid_input} = OID.list_to_string("string")
    end

    test "format functions handle mixed OID value types correctly" do
      # Test that format functions handle object_identifier values correctly
      # Note: pretty_print expects string OIDs as input (final output format)

      test_cases = [
        {{"1.3.6.1.2.1.1.1.0", :object_identifier, [1, 3, 6, 1, 4, 1, 9]}, "1.3.6.1.4.1.9"},
        {{"1.3.6.1.2.1.1.2.0", :object_identifier, "1.3.6.1.4.1.9"}, "1.3.6.1.4.1.9"},
        {{"1.3.6.1.2.1.1.3.0", :integer, 12_345}, "12345"}
      ]

      Enum.each(test_cases, fn {input_tuple, expected_formatted_value} ->
        {oid_string, type, formatted_value} = Format.pretty_print(input_tuple)

        # OID should be string in output
        assert is_binary(oid_string), "OID in output should be string format"

        # Type should be preserved
        {_, original_type, _} = input_tuple
        assert type == original_type, "Type should be preserved"

        # Value formatting should handle both list and string object identifiers
        if original_type == :object_identifier do
          assert formatted_value == expected_formatted_value,
                 "Object identifier should be formatted as string: #{inspect(formatted_value)}"
        end
      end)
    end
  end

  describe "Error Handling and Validation" do
    test "invalid OID inputs are properly rejected" do
      invalid_inputs = [
        "1.3.6.invalid",
        "1.3.6.-1",
        [1, 3, -1],
        [1, 3, "invalid"],
        123,
        nil,
        %{},
        {:invalid}
      ]

      Enum.each(invalid_inputs, fn invalid_input ->
        result = Core.parse_oid(invalid_input)

        assert match?({:error, _}, result),
               "Invalid input #{inspect(invalid_input)} should be rejected"
      end)
    end

    test "empty OID cases have consistent fallbacks" do
      # Only test cases that actually get fallback treatment in parse_oid
      empty_cases = ["", [], "   "]

      Enum.each(empty_cases, fn empty_input ->
        case Core.parse_oid(empty_input) do
          {:ok, [1, 3]} ->
            # Expected fallback
            :ok

          {:ok, other} ->
            flunk("Empty input #{inspect(empty_input)} should fallback to [1, 3], got #{inspect(other)}")

          {:error, reason} ->
            flunk("Empty input #{inspect(empty_input)} should not error, got #{inspect(reason)}")
        end
      end)

      # "." is treated as an invalid OID string, not empty
      assert {:error, _} = Core.parse_oid(".")
    end

    test "type information is preserved throughout operations" do
      # This test validates that our architecture preserves SNMP type information
      # Note: Format functions expect string OIDs (final output format)

      mock_snmp_results = [
        {"1.3.6.1.2.1.1.1.0", :octet_string, "System Description"},
        {"1.3.6.1.2.1.1.2.0", :object_identifier, [1, 3, 6, 1, 4, 1, 9]},
        {"1.3.6.1.2.1.1.3.0", :timeticks, 123_456}
      ]

      # Test that formatting preserves type information
      formatted_results =
        Enum.map(mock_snmp_results, fn result ->
          Format.pretty_print(result)
        end)

      # Every result should have 3-tuple format with preserved type
      mock_snmp_results
      |> Enum.zip(formatted_results)
      |> Enum.each(fn {{_, original_type, _}, {_, formatted_type, _}} ->
        assert formatted_type == original_type,
               "Type information should be preserved: #{original_type} != #{formatted_type}"
      end)

      # OIDs should be converted to strings in final output
      Enum.each(formatted_results, fn {oid_string, _type, _value} ->
        assert is_binary(oid_string), "Final output OID should be string format"

        assert String.match?(oid_string, ~r/^\d+(\.\d+)*$/),
               "OID string should be valid dotted decimal format"
      end)
    end
  end

  describe "Performance and Efficiency" do
    test "no unnecessary conversions in processing chain" do
      # Test that we don't do redundant string<->list conversions
      # This is more of a design validation than a direct test

      list_oid = [1, 3, 6, 1, 2, 1, 1, 1, 0]

      # If we start with a list, parse_oid should not do unnecessary conversions
      assert {:ok, result} = Core.parse_oid(list_oid)
      assert result == list_oid, "List input should return same list without conversion"

      # The result should be the same object (no unnecessary copying)
      # Note: This is more about design efficiency than strict object identity
    end
  end

  describe "Integration Consistency" do
    test "MIB operations maintain OID list format" do
      # Test MIB reverse lookup expects lists
      test_oid = [1, 3, 6, 1, 2, 1, 1, 1, 0]

      # This will return an error since no MIBs are loaded, but should not fail on format
      result = MIB.reverse_lookup(test_oid)

      # Should not fail with format errors
      refute match?({:error, :invalid_oid_format}, result)

      # String version should convert to list internally
      string_result = MIB.reverse_lookup("1.3.6.1.2.1.1.1.0")
      refute match?({:error, :invalid_oid_format}, string_result)
    end

    test "walk operations use consistent OID formats" do
      # Test that internal walk functions expect list OIDs
      # We can't test actual walks without network, but we can test OID resolution

      walk_test_cases = [
        "1.3.6.1.2.1.1",
        "system",
        [1, 3, 6, 1, 2, 1, 1],
        ""
      ]

      Enum.each(walk_test_cases, fn test_input ->
        # All these should be resolved to lists internally
        case Core.parse_oid(test_input) do
          {:ok, result} ->
            assert is_list(result),
                   "Walk OID resolution should return lists: #{inspect(test_input)} -> #{inspect(result)}"

            assert Enum.all?(result, &is_integer/1),
                   "All OID components should be integers: #{inspect(result)}"

          {:error, _} ->
            # Some inputs may be invalid, that's expected
            :ok
        end
      end)
    end
  end
end
