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

  alias SnmpKit.SnmpLib.Walker

  doctest Walker

  @moduletag :walker_test

  describe "Walker.walk_table/3" do
    test "selects appropriate walking strategy based on SNMP version" do
      # v2c should use bulk walking
      assert {:error, _} =
               Walker.walk_table("invalid.host.test", [1, 3, 6, 1, 2, 1, 2, 2],
                 version: :v2c,
                 timeout: 100
               )

      # v1 should use sequential walking
      assert {:error, _} =
               Walker.walk_table("invalid.host.test", [1, 3, 6, 1, 2, 1, 2, 2],
                 version: :v1,
                 timeout: 100
               )
    end

    test "handles table OID normalization" do
      # List OID
      list_oid = [1, 3, 6, 1, 2, 1, 2, 2]
      assert {:error, _} = Walker.walk_table("invalid.host.test", list_oid, timeout: 100)

      # String OID
      string_oid = "1.3.6.1.2.1.2.2"
      assert {:error, _} = Walker.walk_table("invalid.host.test", string_oid, timeout: 100)
    end

    test "validates walking options" do
      opts = [
        community: "public",
        version: :v2c,
        timeout: 5000,
        max_repetitions: 25,
        max_retries: 3,
        retry_delay: 1000
      ]

      assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1, 2, 1, 2, 2], opts)
    end

    test "handles bulk size configuration" do
      # Small bulk size
      small_opts = [max_repetitions: 5, timeout: 100]
      assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1], small_opts)

      # Large bulk size
      large_opts = [max_repetitions: 100, timeout: 100]
      assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1], large_opts)
    end
  end

  describe "Walker.walk_subtree/3" do
    test "walks entire subtree under base OID" do
      # system subtree
      base_oid = [1, 3, 6, 1, 2, 1, 1]

      assert {:error, _} = Walker.walk_subtree("192.168.255.254", base_oid, timeout: 50)
    end

    test "handles subtree boundary detection" do
      # Should stop when OIDs no longer match prefix
      assert {:error, _} = Walker.walk_subtree("192.168.255.254", [1, 3, 6, 1], timeout: 50)
    end

    test "uses appropriate strategy for subtree walking" do
      opts_v1 = [version: :v1, timeout: 100]
      opts_v2c = [version: :v2c, timeout: 100]

      # Both should attempt but fail due to invalid host
      assert {:error, _} = Walker.walk_subtree("invalid.host.test", [1, 3, 6, 1], opts_v1)
      assert {:error, _} = Walker.walk_subtree("invalid.host.test", [1, 3, 6, 1], opts_v2c)
    end
  end

  describe "Walker.stream_table/3" do
    test "returns a stream for table walking" do
      stream = Walker.stream_table("invalid.host.test", [1, 3, 6, 1, 2, 1, 2, 2], timeout: 100)

      # Should be enumerable
      assert Enumerable.impl_for(stream)

      # Should handle empty results gracefully when enumerated
      # (will be empty due to invalid host)
      results = Enum.take(stream, 5)
      assert is_list(results)
    end

    test "supports streaming configuration options" do
      opts = [
        chunk_size: 10,
        max_repetitions: 20,
        timeout: 100
      ]

      stream = Walker.stream_table("invalid.host.test", [1, 3, 6, 1, 2, 1, 2, 2], opts)

      # Should create stream with custom options
      assert Enumerable.impl_for(stream)
    end

    test "integrates with Stream operations" do
      stream = Walker.stream_table("invalid.host.test", [1, 3, 6, 1, 2, 1, 2, 2], timeout: 100)

      # Should work with standard Stream operations
      filtered =
        stream
        # Flatten chunks
        |> Stream.flat_map(& &1)
        |> Stream.filter(fn {_oid, value} -> value != nil end)
        |> Stream.take(10)

      # Should enumerate without error (though empty due to invalid host)
      results = Enum.to_list(filtered)
      assert is_list(results)
    end
  end

  describe "Walker.walk_column/3" do
    test "extracts single column from table" do
      # Interface description column
      column_oid = [1, 3, 6, 1, 2, 1, 2, 2, 1, 2]

      assert {:error, _} = Walker.walk_column("invalid.host.test", column_oid, timeout: 100)
    end

    test "returns index-value pairs for column data" do
      # Test that the function signature is correct
      assert is_function(&Walker.walk_column/3)

      # Would test actual column extraction with mock data
      column_oid = [1, 3, 6, 1, 2, 1, 2, 2, 1, 1]
      assert {:error, _} = Walker.walk_column("invalid.host.test", column_oid, timeout: 100)
    end
  end

  describe "Walker.estimate_table_size/3" do
    test "estimates table size by walking first column" do
      table_oid = [1, 3, 6, 1, 2, 1, 2, 2]

      assert {:error, _} =
               Walker.estimate_table_size("invalid.host.test", table_oid, timeout: 100)
    end

    test "provides count estimate for planning" do
      # Test interface for size estimation
      assert is_function(&Walker.estimate_table_size/3)

      # Should return count when successful
      # routing table
      table_oid = [1, 3, 6, 1, 2, 1, 4, 21]

      assert {:error, _} =
               Walker.estimate_table_size("invalid.host.test", table_oid, timeout: 100)
    end
  end

  describe "Walker strategy selection" do
    test "chooses bulk walking for SNMPv2c" do
      opts = [version: :v2c, timeout: 100]

      # Should attempt bulk operations
      assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1], opts)
    end

    test "falls back to sequential for SNMPv1" do
      opts = [version: :v1, timeout: 100]

      # Should use sequential walking
      assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1], opts)
    end

    test "handles strategy-specific parameters" do
      bulk_opts = [version: :v2c, max_repetitions: 50, timeout: 100]
      seq_opts = [version: :v1, timeout: 100]

      # Both should handle appropriately
      assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1], bulk_opts)
      assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1], seq_opts)
    end
  end

  describe "Walker error handling" do
    test "handles network timeouts gracefully" do
      opts = [timeout: 20]

      # Should timeout quickly
      assert {:error, _} = Walker.walk_table("192.168.255.255", [1, 3, 6, 1], opts)
    end

    @tag :skip
    test "implements retry logic" do
      # Test with unreachable host
      opts = [max_retries: 1, retry_delay: 50, timeout: 20]

      # Should retry on failures
      start_time = System.monotonic_time(:millisecond)
      {:error, _} = Walker.walk_table("192.168.255.255", [1, 3, 6, 1], opts)
      end_time = System.monotonic_time(:millisecond)

      # Should take longer due to retries
      # At least one retry delay
      assert end_time - start_time >= 50
    end

    test "handles table boundary conditions" do
      # Test with various table scenarios
      assert {:error, _} = Walker.walk_table("192.168.255.254", [1, 3, 6, 1], timeout: 20)
      assert {:error, _} = Walker.walk_subtree("192.168.255.254", [1, 3, 6, 1], timeout: 20)
    end

    test "gracefully handles empty tables" do
      # Should handle tables with no entries
      assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1, 99], timeout: 100)
    end
  end

  describe "Walker performance features" do
    test "supports adaptive bulk sizing" do
      opts = [adaptive_bulk: true, max_repetitions: 25, timeout: 100]

      # Should attempt adaptive sizing
      assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1], opts)
    end

    test "handles concurrent walking operations" do
      # Test multiple concurrent table walks
      tasks =
        Enum.map(1..3, fn i ->
          Task.async(fn ->
            Walker.walk_table("invalid.host.test", [1, 3, 6, 1, i], timeout: 100)
          end)
        end)

      results = Task.await_many(tasks, 1000)

      # All should complete (with errors due to invalid host)
      assert length(results) == 3
      assert Enum.all?(results, fn result -> match?({:error, _}, result) end)
    end

    @tag :performance
    test "streaming is memory efficient" do
      # Create stream but don't fully enumerate
      stream = Walker.stream_table("invalid.host.test", [1, 3, 6, 1, 2, 1, 2, 2], timeout: 100)

      # Take just a few items (should not load entire table)
      partial_results = Enum.take(stream, 2)

      # Should handle partial enumeration gracefully
      assert is_list(partial_results)
    end

    @tag :performance
    test "bulk operations are more efficient than sequential" do
      # This would test actual performance differences
      # For now, verify that both strategies are available

      bulk_opts = [version: :v2c, max_repetitions: 50, timeout: 100]
      seq_opts = [version: :v1, timeout: 100]

      # Both should be callable
      assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1], bulk_opts)
      assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1], seq_opts)
    end
  end

  describe "Walker integration" do
    test "integrates with Manager for individual operations" do
      # Walker should use Manager for SNMP operations
      assert is_function(&Walker.walk_table/3)
      assert is_function(&Walker.estimate_table_size/3)

      # Should handle Manager responses appropriately
      assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1], timeout: 100)
    end

    test "uses SnmpKit.SnmpLib.OID for OID manipulation" do
      # Should handle both string and list OIDs
      list_oid = [1, 3, 6, 1, 2, 1, 2, 2]
      string_oid = "1.3.6.1.2.1.2.2"

      # Both should work
      assert {:error, _} = Walker.walk_table("invalid.host.test", list_oid, timeout: 100)
      assert {:error, _} = Walker.walk_table("invalid.host.test", string_oid, timeout: 100)
    end

    test "handles SnmpLib error responses correctly" do
      # Should properly handle various SNMP error conditions
      # These would be tested with actual SNMP responses
      assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1], timeout: 100)
    end
  end

  describe "Walker option validation" do
    test "validates and applies default options" do
      # Test with minimal options
      assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1])

      # Test with custom options
      custom_opts = [
        community: "private",
        timeout: 10_000,
        max_repetitions: 100,
        chunk_size: 50
      ]

      assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1], custom_opts)
    end

    test "handles invalid option values gracefully" do
      # Test with various option edge cases
      opts = [
        # Very short timeout
        timeout: 1,
        # Very large bulk size
        max_repetitions: 1000,
        # No retries
        max_retries: 0
      ]

      assert {:error, _} = Walker.walk_table("invalid.host.test", [1, 3, 6, 1], opts)
    end
  end
end
