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

  alias SnmpKit.SnmpMgr.Bulk

  describe "get_bulk/3 version validation" do
    test "rejects SNMPv1 with error" do
      result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], version: :v1)

      assert {:error, {:unsupported_operation, :get_bulk_requires_v2c}} = result
    end

    test "rejects SNMPv3 with error" do
      result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], version: :v3)

      assert {:error, {:unsupported_operation, :get_bulk_requires_v2c}} = result
    end

    test "accepts SNMPv2c (explicit)" do
      # Will timeout or error without proper SNMP infrastructure
      result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], version: :v2c, timeout: 100)

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

    test "defaults to SNMPv2c when version not specified" do
      # Should use v2c by default
      result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], timeout: 100)

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

  describe "get_bulk/3 OID handling" do
    test "accepts single OID as list" do
      result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], timeout: 100)

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

    test "accepts single OID as string" do
      result = Bulk.get_bulk("192.168.1.1", "1.3.6.1.2.1.1", timeout: 100)

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

    test "accepts multiple OIDs as list" do
      result = Bulk.get_bulk("192.168.1.1", [[1, 3, 6, 1, 2, 1, 1], [1, 3, 6, 1, 2, 1, 2]], timeout: 100)

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

    test "converts single OID to list internally" do
      # Single OID should be wrapped in a list
      result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], timeout: 100)

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

    test "returns error for invalid OID" do
      result = Bulk.get_bulk("192.168.1.1", "invalid.oid", timeout: 100)

      assert {:error, _} = result
    end
  end

  describe "get_bulk/3 options handling" do
    test "uses default max_repetitions when not specified" do
      result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], timeout: 100)

      # Default is 30
      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end

    test "uses specified max_repetitions" do
      result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], max_repetitions: 10, timeout: 100)

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

    test "uses default non_repeaters when not specified" do
      result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], timeout: 100)

      # Default is 0
      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end

    test "uses specified non_repeaters" do
      result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], non_repeaters: 1, timeout: 100)

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

    test "forces version to v2c even if different version passed" do
      # Should override any other version setting
      result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], timeout: 100)

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

    test "accepts community string option" do
      result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], community: "private", timeout: 100)

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

    test "accepts port option" do
      result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], port: 1161, timeout: 100)

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

    test "accepts timeout option" do
      result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], timeout: 5000)

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

  describe "get_table_bulk/3" do
    test "retrieves table using GETBULK" do
      result = Bulk.get_table_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 2, 2], timeout: 100)

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

    test "accepts string table OID" do
      result = Bulk.get_table_bulk("192.168.1.1", "1.3.6.1.2.1.2.2", timeout: 100)

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

    test "uses default max_entries when not specified" do
      result = Bulk.get_table_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 2, 2], timeout: 100)

      # Default is 1000
      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end

    test "respects max_entries option" do
      result = Bulk.get_table_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 2, 2], max_entries: 100, timeout: 100)

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

    test "respects max_repetitions option" do
      result = Bulk.get_table_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 2, 2], max_repetitions: 20, timeout: 100)

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

    test "returns error for invalid table OID" do
      result = Bulk.get_table_bulk("192.168.1.1", "invalid.table", timeout: 100)

      assert {:error, _} = result
    end

    test "accepts community string" do
      result = Bulk.get_table_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 2, 2], community: "public", timeout: 100)

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

  describe "walk_bulk/3" do
    test "performs bulk walk of subtree" do
      result = Bulk.walk_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], timeout: 100)

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

    test "accepts string root OID" do
      result = Bulk.walk_bulk("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_entries when not specified" do
      result = Bulk.walk_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], timeout: 100)

      # Default is 1000
      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end

    test "respects max_entries option" do
      result = Bulk.walk_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], max_entries: 50, timeout: 100)

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

    test "respects max_repetitions option" do
      result = Bulk.walk_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], max_repetitions: 15, timeout: 100)

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

    test "returns error for invalid root OID" do
      result = Bulk.walk_bulk("192.168.1.1", "invalid", timeout: 100)

      assert {:error, _} = result
    end

    test "accepts community string" do
      result = Bulk.walk_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], community: "private", timeout: 100)

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

    test "accepts port option" do
      result = Bulk.walk_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], port: 1161, timeout: 100)

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

  describe "get_bulk_multi/2" do
    test "performs concurrent GETBULK operations" do
      requests = [
        {"192.168.1.1", [1, 3, 6, 1, 2, 1, 1, 1, 0]},
        {"192.168.1.2", [1, 3, 6, 1, 2, 1, 1, 2, 0]},
        {"192.168.1.3", [1, 3, 6, 1, 2, 1, 1, 3, 0]}
      ]

      results = Bulk.get_bulk_multi(requests, timeout: 100)

      assert is_list(results)
      assert length(results) == 3
      # Each result is either {:ok, _} or {:error, _}
      assert Enum.all?(results, fn result ->
               match?({:ok, _}, result) or match?({:error, _}, result)
             end)
    end

    test "handles empty request list" do
      results = Bulk.get_bulk_multi([], timeout: 100)

      assert results == []
    end

    test "handles single request" do
      requests = [{"192.168.1.1", [1, 3, 6, 1, 2, 1, 1, 1, 0]}]

      results = Bulk.get_bulk_multi(requests, timeout: 100)

      assert length(results) == 1
    end

    test "returns timeout error for tasks that don't complete" do
      requests = [
        {"192.168.1.1", [1, 3, 6, 1, 2, 1, 1, 1, 0]},
        {"192.168.1.2", [1, 3, 6, 1, 2, 1, 1, 2, 0]}
      ]

      # Very short timeout to force timeout
      results = Bulk.get_bulk_multi(requests, timeout: 1)

      assert length(results) == 2
      # All should error (either timeout or other error like invalid_oid_input)
      assert Enum.all?(results, fn result ->
               match?({:error, _}, result)
             end)
    end

    test "uses default timeout when not specified" do
      requests = [{"192.168.1.1", [1, 3, 6, 1, 2, 1, 1, 1, 0]}]

      results = Bulk.get_bulk_multi(requests)

      assert length(results) == 1
    end

    test "passes options to individual requests" do
      requests = [
        {"192.168.1.1", [1, 3, 6, 1, 2, 1, 1, 1, 0]},
        {"192.168.1.2", [1, 3, 6, 1, 2, 1, 1, 2, 0]}
      ]

      results = Bulk.get_bulk_multi(requests, community: "public", max_repetitions: 10, timeout: 100)

      assert length(results) == 2
    end

    test "handles task failures gracefully" do
      requests = [
        {"192.168.1.1", "invalid.oid"},
        {"192.168.1.2", [1, 3, 6, 1, 2, 1, 1, 2, 0]}
      ]

      results = Bulk.get_bulk_multi(requests, timeout: 100)

      assert length(results) == 2
      # First should error due to invalid OID
      assert match?({:error, _}, Enum.at(results, 0))
    end

    test "accepts string OIDs in requests" do
      requests = [
        {"192.168.1.1", "1.3.6.1.2.1.1.1.0"},
        {"192.168.1.2", "1.3.6.1.2.1.1.2.0"}
      ]

      results = Bulk.get_bulk_multi(requests, timeout: 100)

      assert length(results) == 2
    end

    test "accepts mixed OID formats in requests" do
      requests = [
        {"192.168.1.1", [1, 3, 6, 1, 2, 1, 1, 1, 0]},
        {"192.168.1.2", "1.3.6.1.2.1.1.2.0"}
      ]

      results = Bulk.get_bulk_multi(requests, timeout: 100)

      assert length(results) == 2
    end
  end

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

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

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

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

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

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

  describe "error handling" do
    test "handles network timeout" do
      result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], timeout: 1)

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

    test "handles invalid target" do
      result = Bulk.get_bulk("999.999.999.999", [1, 3, 6, 1, 2, 1, 1], timeout: 100)

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

    test "handles unreachable target" do
      result = Bulk.get_bulk("192.168.255.254", [1, 3, 6, 1, 2, 1, 1], timeout: 100)

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

    test "returns error for malformed options" do
      result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], max_repetitions: -1, timeout: 100)

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

  describe "edge cases" do
    test "handles empty table results" do
      result = Bulk.get_table_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 999], timeout: 100)

      # Empty table should return {:ok, []}
      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end

    test "handles max_entries limit reached" do
      result = Bulk.get_table_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 2, 2], max_entries: 1, timeout: 100)

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

    test "handles zero max_entries" do
      result = Bulk.get_table_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 2, 2], max_entries: 0, timeout: 100)

      # Zero max_entries should return empty results immediately
      assert match?({:ok, _}, result) or match?({:error, _}, result)
    end

    test "handles very large max_repetitions" do
      result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], max_repetitions: 1000, timeout: 100)

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

    test "handles zero max_repetitions" do
      result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], max_repetitions: 0, timeout: 100)

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

    test "handles single OID result" do
      result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1, 1, 0], max_repetitions: 1, timeout: 100)

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

  describe "get_bulk/3 with non_repeaters" do
    test "uses non_repeaters for multiple OIDs" do
      # When retrieving multiple OIDs, non_repeaters controls how many are scalar
      result =
        Bulk.get_bulk("192.168.1.1", [[1, 3, 6, 1, 2, 1, 1], [1, 3, 6, 1, 2, 1, 2]], non_repeaters: 1, timeout: 100)

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

    test "defaults non_repeaters to 0" do
      result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], timeout: 100)

      # Should use non_repeaters: 0 by default
      assert match?({:error, _}, result) or match?({:ok, _}, result)
    end

    test "accepts large non_repeaters value" do
      result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], non_repeaters: 100, timeout: 100)

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

  describe "walk_bulk/3 vs get_table_bulk/3" do
    test "walk_bulk and get_table_bulk produce similar results for tables" do
      # Both should work for walking tables
      walk_result = Bulk.walk_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 2, 2], timeout: 100)
      table_result = Bulk.get_table_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 2, 2], timeout: 100)

      # Both should either succeed or fail, but with same error type
      assert (match?({:ok, _}, walk_result) and match?({:ok, _}, table_result)) or
               (match?({:error, _}, walk_result) and match?({:error, _}, table_result))
    end

    test "walk_bulk works for non-table subtrees" do
      result = Bulk.walk_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], timeout: 100)

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

  describe "multiple options combinations" do
    test "accepts all options together" do
      opts = [
        version: :v2c,
        community: "public",
        port: 161,
        timeout: 5000,
        max_repetitions: 20,
        non_repeaters: 0,
        max_entries: 500
      ]

      result = Bulk.walk_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], opts)

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

    test "get_bulk with all options" do
      opts = [
        community: "private",
        port: 1161,
        timeout: 3000,
        max_repetitions: 15,
        non_repeaters: 1
      ]

      result = Bulk.get_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 1], opts)

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

    test "get_table_bulk with all options" do
      opts = [
        community: "public",
        port: 161,
        timeout: 2000,
        max_repetitions: 25,
        max_entries: 100
      ]

      result = Bulk.get_table_bulk("192.168.1.1", [1, 3, 6, 1, 2, 1, 2, 2], opts)

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

  describe "get_bulk_multi/2 with various request counts" do
    test "handles large number of concurrent requests" do
      requests =
        for i <- 1..20 do
          {"192.168.1.#{i}", [1, 3, 6, 1, 2, 1, 1, 1, 0]}
        end

      results = Bulk.get_bulk_multi(requests, timeout: 100)

      assert length(results) == 20
    end

    test "all concurrent requests timeout consistently" do
      requests =
        for i <- 1..5 do
          {"192.168.255.#{i}", [1, 3, 6, 1, 2, 1, 1, 1, 0]}
        end

      results = Bulk.get_bulk_multi(requests, timeout: 10)

      assert length(results) == 5
      # All should timeout or error
      assert Enum.all?(results, fn result -> match?({:error, _}, result) end)
    end
  end
end
