defmodule SnmpKit.SnmpMgr.WalkTest do
  use ExUnit.Case, async: true

  alias SnmpKit.SnmpMgr.Walk

  # Test helpers to mock Core module behavior
  defmodule CoreMock do
    @moduledoc false

    def send_get_next_request(_target, oid, _opts) do
      # Simulate walking through a small tree
      case oid do
        [1, 3, 6, 1, 2, 1, 1] ->
          {:ok, {"1.3.6.1.2.1.1.1.0", :octet_string, "Test Device"}}

        [1, 3, 6, 1, 2, 1, 1, 1, 0] ->
          {:ok, {"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, 2, 0] ->
          {:ok, {"1.3.6.1.2.1.1.3.0", :timeticks, 12_345}}

        [1, 3, 6, 1, 2, 1, 1, 3, 0] ->
          # Out of scope - different subtree
          {:ok, {"1.3.6.1.2.1.2.1.0", :integer, 100}}

        # Simulate end of MIB view
        [1, 3, 6, 1, 2, 1, 9] ->
          {:error, :end_of_mib_view}

        [1, 3, 6, 1, 2, 1, 10] ->
          {:error, {:snmp_error, :noSuchName}}

        # Timeout simulation
        [1, 3, 6, 1, 2, 1, 99] ->
          {:error, :timeout}

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

    def parse_oid(oid) when is_list(oid), do: {:ok, oid}

    def parse_oid(oid) when is_binary(oid) do
      case oid do
        "1.3.6.1.2.1.1" -> {:ok, [1, 3, 6, 1, 2, 1, 1]}
        "1.3.6.1.2.1.2" -> {:ok, [1, 3, 6, 1, 2, 1, 2]}
        "invalid" -> {:error, :invalid_oid}
        _ -> {:ok, oid |> String.split(".") |> Enum.map(&String.to_integer/1)}
      end
    end
  end

  setup do
    # Mock the Core module for v1 walks
    original_core = Application.get_env(:snmpkit, :core_module)

    on_exit(fn ->
      if original_core do
        Application.put_env(:snmpkit, :core_module, original_core)
      else
        Application.delete_env(:snmpkit, :core_module)
      end
    end)

    :ok
  end

  describe "walk/3 with SNMPv2c" do
    test "delegates to Bulk.walk_bulk for v2c" do
      # This test verifies that v2c walks use the bulk implementation
      # We'll test with a simple OID and verify the delegation happens
      # The actual bulk implementation is tested in bulk_test.exs

      result = Walk.walk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], version: :v2c, timeout: 100)

      # Expect timeout or error since we're not mocking Bulk
      # This test just verifies the code path is hit
      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end

    test "accepts string OID for v2c" do
      result = Walk.walk("192.168.1.1", "1.3.6.1.2.1.1", version: :v2c, timeout: 100)

      # Should attempt to delegate to Bulk
      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end

    test "accepts max_repetitions option for v2c" do
      result = Walk.walk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], version: :v2c, max_repetitions: 10, timeout: 100)

      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end
  end

  describe "walk/3 with SNMPv1" do
    test "performs iterative GETNEXT walk for v1" do
      # This would require mocking Core.send_get_next_request
      # For now, test that it attempts the operation

      result = Walk.walk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], version: :v1, timeout: 100)

      # Will likely timeout or error without proper mock
      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end

    test "uses max_iterations option for v1" do
      result = Walk.walk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], version: :v1, max_iterations: 10, timeout: 100)

      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end

    test "removes max_repetitions from v1 options" do
      # max_repetitions is not valid for v1, should be removed
      result = Walk.walk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], version: :v1, max_repetitions: 20, timeout: 100)

      # Should not crash, max_repetitions should be ignored
      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end

    test "returns error for invalid OID" do
      result = Walk.walk("192.168.1.1", "invalid", version: :v1, timeout: 100)

      assert {:error, _} = result
    end
  end

  describe "walk_table/3 with SNMPv2c" do
    test "delegates to Bulk.get_table_bulk for v2c" do
      result = Walk.walk_table("192.168.1.1", [1, 3, 6, 1, 2, 1, 2, 2], version: :v2c, timeout: 100)

      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end

    test "accepts string table OID for v2c" do
      result = Walk.walk_table("192.168.1.1", "1.3.6.1.2.1.2.2", version: :v2c, timeout: 100)

      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end
  end

  describe "walk_table/3 with SNMPv1" do
    test "performs iterative GETNEXT walk for v1 tables" do
      result = Walk.walk_table("192.168.1.1", [1, 3, 6, 1, 2, 1, 2, 2], version: :v1, timeout: 100)

      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end

    test "uses max_iterations for v1 table walks" do
      result = Walk.walk_table("192.168.1.1", [1, 3, 6, 1, 2, 1, 2, 2], version: :v1, max_iterations: 5, timeout: 100)

      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end

    test "returns error for invalid table OID" do
      result = Walk.walk_table("192.168.1.1", "invalid", version: :v1, timeout: 100)

      assert {:error, _} = result
    end
  end

  describe "walk_column/3" do
    test "walks a specific table column" do
      result = Walk.walk_column("192.168.1.1", [1, 3, 6, 1, 2, 1, 2, 2, 1, 2], timeout: 100)

      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end

    test "accepts string column OID" do
      result = Walk.walk_column("192.168.1.1", "1.3.6.1.2.1.2.2.1.2", timeout: 100)

      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end

    test "respects max_iterations option" do
      result = Walk.walk_column("192.168.1.1", [1, 3, 6, 1, 2, 1, 2, 2, 1, 2], max_iterations: 3, timeout: 100)

      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end

    test "returns error for invalid column OID" do
      result = Walk.walk_column("192.168.1.1", "invalid", timeout: 100)

      assert {:error, _} = result
    end
  end

  describe "walk/3 default behavior" do
    test "defaults to v2c when no version specified" do
      # Default should delegate to Bulk (v2c)
      result = Walk.walk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], timeout: 100)

      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end

    test "uses default max_iterations when not specified for v1" do
      result = Walk.walk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], version: :v1, timeout: 100)

      # Should use default max_iterations (100)
      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end

    test "uses default timeout when not specified" do
      result = Walk.walk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], version: :v1)

      # Should use default timeout (5000ms)
      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end
  end

  describe "OID resolution" do
    test "accepts list format OID" do
      result = Walk.walk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], version: :v1, timeout: 100)

      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end

    test "accepts string format OID" do
      result = Walk.walk("192.168.1.1", "1.3.6.1.2.1.1", version: :v1, timeout: 100)

      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end

    test "handles OID resolution errors gracefully" do
      result = Walk.walk("192.168.1.1", "invalid.oid.format", version: :v1, timeout: 100)

      assert {:error, _} = result
    end
  end

  describe "error handling" do
    test "handles timeout errors gracefully" do
      # Test that timeout errors don't crash the walk
      result = Walk.walk("192.168.1.1", [1, 3, 6, 1, 2, 1, 99], version: :v1, timeout: 100)

      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end

    test "handles end of MIB view errors" do
      result = Walk.walk("192.168.1.1", [1, 3, 6, 1, 2, 1, 9], version: :v1, timeout: 100)

      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end

    test "handles no such name errors" do
      result = Walk.walk("192.168.1.1", [1, 3, 6, 1, 2, 1, 10], version: :v1, timeout: 100)

      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end
  end

  describe "options handling" do
    test "accepts community string option" do
      result = Walk.walk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], version: :v1, community: "private", timeout: 100)

      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end

    test "accepts port option" do
      result = Walk.walk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], version: :v1, port: 161, timeout: 100)

      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end

    test "accepts multiple options" do
      opts = [
        version: :v1,
        community: "public",
        port: 161,
        timeout: 2000,
        max_iterations: 50
      ]

      result = Walk.walk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], opts)

      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end
  end

  describe "target formats" do
    test "accepts IP address as target" do
      result = Walk.walk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], version: :v1, timeout: 100)

      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end

    test "accepts hostname as target" do
      result = Walk.walk("device.local", [1, 3, 6, 1, 2, 1, 1], version: :v1, timeout: 100)

      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end

    test "accepts target with port" do
      result = Walk.walk("192.168.1.1:161", [1, 3, 6, 1, 2, 1, 1], version: :v1, timeout: 100)

      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end
  end

  describe "walk_table/3 default behavior" do
    test "defaults to v2c for table walks" do
      result = Walk.walk_table("192.168.1.1", [1, 3, 6, 1, 2, 1, 2, 2], timeout: 100)

      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end
  end

  describe "walk_column/3 default behavior" do
    test "uses v1-style walk for columns" do
      # walk_column always uses iterative GETNEXT
      result = Walk.walk_column("192.168.1.1", [1, 3, 6, 1, 2, 1, 2, 2, 1, 2], timeout: 100)

      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end
  end

  describe "edge cases" do
    test "handles empty walk results gracefully" do
      # Test walking a non-existent subtree
      result = Walk.walk("192.168.1.1", [1, 3, 6, 1, 2, 1, 999], version: :v1, timeout: 100)

      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end

    test "handles max_iterations limit" do
      # Test that walk stops at max_iterations
      result = Walk.walk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], version: :v1, max_iterations: 1, timeout: 100)

      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end

    test "handles zero max_iterations" do
      result = Walk.walk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], version: :v1, max_iterations: 0, timeout: 100)

      # Should return empty results or error
      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end
  end
end
