defmodule SnmpKit.SnmpMgrIntegrationTest do
  use ExUnit.Case, async: false

  alias SnmpKit.SnmpMgr

  @moduletag :integration
  @moduletag :snmpkit

  describe "SnmpMgr get/3" do
    test "successfully retrieves sysUpTime from localhost simulator" do
      target = "127.0.0.1"
      oid = "1.3.6.1.2.1.1.3.0"
      opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]

      case SnmpMgr.get(target, oid, opts) do
        {:ok, value} ->
          assert is_tuple(value)
          {type, _data} = value
          assert type == :timeticks

        {:error, :timeout} ->
          # Simulator not running, skip test
          :ok

        {:error, reason} ->
          flunk("Unexpected error: #{inspect(reason)}")
      end
    end

    test "handles string OID format" do
      target = "127.0.0.1"
      oid = "1.3.6.1.2.1.1.1.0"
      opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]

      result = SnmpMgr.get(target, oid, opts)
      assert match?({:ok, _}, result) or match?({:error, :timeout}, result)
    end

    test "handles list OID format" do
      target = "127.0.0.1"
      oid = [1, 3, 6, 1, 2, 1, 1, 1, 0]
      opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]

      result = SnmpMgr.get(target, oid, opts)
      assert match?({:ok, _}, result) or match?({:error, :timeout}, result)
    end

    test "returns error for invalid OID" do
      target = "127.0.0.1"
      oid = "1.3.6.1.99.99.99.99"
      opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]

      case SnmpMgr.get(target, oid, opts) do
        {:error, _reason} -> :ok
        {:ok, _} -> flunk("Expected error for invalid OID")
      end
    end

    test "handles timeout gracefully" do
      target = "192.0.2.1"
      # RFC 5737 TEST-NET-1 address that should not route
      oid = "1.3.6.1.2.1.1.1.0"
      opts = [community: "public", version: :v2c, port: 161, timeout: 1000]

      assert {:error, :timeout} = SnmpMgr.get(target, oid, opts)
    end

    test "returns error for wrong community string" do
      target = "127.0.0.1"
      oid = "1.3.6.1.2.1.1.1.0"
      opts = [community: "wrong_community", version: :v2c, port: 1161, timeout: 5000]

      case SnmpMgr.get(target, oid, opts) do
        {:error, _reason} ->
          :ok

        {:ok, _} ->
          # Simulator may allow any community
          :ok
      end
    end
  end

  describe "SnmpMgr walk/3" do
    test "successfully walks interface table" do
      target = "127.0.0.1"
      oid = "1.3.6.1.2.1.2.2.1.2"
      opts = [community: "public", version: :v2c, port: 1161, timeout: 10_000]

      case SnmpMgr.walk(target, oid, opts) do
        {:ok, results} ->
          assert is_list(results)

          case results do
            [first | _rest] ->
              # Verify structure of results
              assert is_map(first)
              assert Map.has_key?(first, :oid)
              assert Map.has_key?(first, :value)

            [] ->
              :ok
          end

        {:error, :timeout} ->
          # Simulator not running
          :ok

        {:error, reason} ->
          # Other errors are acceptable (no such object, etc.)
          assert is_atom(reason)
      end
    end

    test "returns empty list for non-existent subtree" do
      target = "127.0.0.1"
      oid = "1.3.6.1.99.99.99"
      opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]

      case SnmpMgr.walk(target, oid, opts) do
        {:ok, results} ->
          assert is_list(results)
          assert results == []

        {:error, _reason} ->
          # Error is also acceptable
          :ok
      end
    end

    test "handles walk timeout" do
      target = "192.0.2.1"
      oid = "1.3.6.1.2.1.2.2.1"
      opts = [community: "public", version: :v2c, port: 161, timeout: 1000]

      assert {:error, :timeout} = SnmpMgr.walk(target, oid, opts)
    end

    test "walk result format is consistent" do
      target = "127.0.0.1"
      oid = "1.3.6.1.2.1.1"
      opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]

      case SnmpMgr.walk(target, oid, opts) do
        {:ok, results} when is_list(results) ->
          # All results should be maps with :oid and :value keys
          for result <- results do
            assert is_map(result)
            assert Map.has_key?(result, :oid)
            assert Map.has_key?(result, :value)
            assert is_binary(result.oid) or is_list(result.oid)
          end

        {:error, :timeout} ->
          :ok

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

  describe "SnmpMgr get_bulk/3" do
    test "successfully performs get_bulk operation" do
      target = "127.0.0.1"
      oid = "1.3.6.1.2.1.2.2.1.10"

      opts = [
        community: "public",
        version: :v2c,
        port: 1161,
        timeout: 5000,
        max_repetitions: 10
      ]

      case SnmpMgr.get_bulk(target, oid, opts) do
        {:ok, results} ->
          assert is_list(results)

          case results do
            [first | _rest] ->
              assert is_map(first)
              assert Map.has_key?(first, :oid)
              assert Map.has_key?(first, :value)

            [] ->
              :ok
          end

        {:error, :timeout} ->
          :ok

        {:error, reason} ->
          assert is_atom(reason)
      end
    end

    test "respects max_repetitions parameter" do
      target = "127.0.0.1"
      oid = "1.3.6.1.2.1.2.2.1"

      opts = [
        community: "public",
        version: :v2c,
        port: 1161,
        timeout: 5000,
        max_repetitions: 5
      ]

      case SnmpMgr.get_bulk(target, oid, opts) do
        {:ok, results} ->
          # Should get at most max_repetitions results per column
          assert is_list(results)

        {:error, :timeout} ->
          :ok

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

    test "get_bulk only works with SNMPv2c" do
      target = "127.0.0.1"
      oid = "1.3.6.1.2.1.2.2.1.10"

      # Try with v1 - should not support get_bulk
      opts_v1 = [community: "public", version: :v1, port: 1161, timeout: 5000]

      case SnmpMgr.get_bulk(target, oid, opts_v1) do
        {:error, _reason} ->
          # Expected - v1 doesn't support get_bulk
          :ok

        {:ok, _} ->
          # Some implementations may allow it or fall back
          :ok
      end

      # Try with v2c - should work
      opts_v2c = [community: "public", version: :v2c, port: 1161, timeout: 5000]

      case SnmpMgr.get_bulk(target, oid, opts_v2c) do
        {:ok, results} ->
          assert is_list(results)

        {:error, :timeout} ->
          :ok

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

  describe "SnmpMgr with different versions" do
    test "SNMPv1 get operation" do
      target = "127.0.0.1"
      oid = "1.3.6.1.2.1.1.3.0"
      opts = [community: "public", version: :v1, port: 1161, timeout: 5000]

      result = SnmpMgr.get(target, oid, opts)
      assert match?({:ok, _}, result) or match?({:error, _}, result)
    end

    test "SNMPv2c get operation" do
      target = "127.0.0.1"
      oid = "1.3.6.1.2.1.1.3.0"
      opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]

      result = SnmpMgr.get(target, oid, opts)
      assert match?({:ok, _}, result) or match?({:error, _}, result)
    end
  end

  describe "SnmpMgr error handling" do
    test "handles network errors gracefully" do
      target = "192.0.2.1"
      oid = "1.3.6.1.2.1.1.1.0"
      opts = [community: "public", version: :v2c, port: 161, timeout: 1000]

      assert {:error, :timeout} = SnmpMgr.get(target, oid, opts)
    end

    test "handles invalid target format" do
      target = "not-a-valid-ip"
      oid = "1.3.6.1.2.1.1.1.0"
      opts = [community: "public", version: :v2c, port: 161, timeout: 1000]

      case SnmpMgr.get(target, oid, opts) do
        {:error, _reason} ->
          :ok

        {:ok, _} ->
          :ok
          # Some systems may resolve hostnames
      end
    end

    test "handles malformed OID" do
      target = "127.0.0.1"
      # Invalid OID format
      oid = "not.an.oid"
      opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]

      case SnmpMgr.get(target, oid, opts) do
        {:error, _reason} ->
          # Expected
          :ok

        {:ok, _} ->
          # Some parsers may handle this
          :ok
      end
    end
  end

  describe "SnmpMgr real-world scenarios" do
    test "retrieve system information bundle" do
      target = "127.0.0.1"
      opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]

      system_oids = [
        "1.3.6.1.2.1.1.1.0",
        # sysDescr
        "1.3.6.1.2.1.1.2.0",
        # sysObjectID
        "1.3.6.1.2.1.1.3.0",
        # sysUpTime
        "1.3.6.1.2.1.1.4.0",
        # sysContact
        "1.3.6.1.2.1.1.5.0",
        # sysName
        "1.3.6.1.2.1.1.6.0"
        # sysLocation
      ]

      # Try to get all system info
      results =
        Enum.map(system_oids, fn oid ->
          SnmpMgr.get(target, oid, opts)
        end)

      # At least some should succeed (if simulator running) or all timeout
      assert Enum.all?(results, fn result ->
               match?({:ok, _}, result) or match?({:error, _}, result)
             end)
    end

    test "walk and count interfaces" do
      target = "127.0.0.1"
      oid = "1.3.6.1.2.1.2.2.1.1"
      # ifIndex
      opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]

      case SnmpMgr.walk(target, oid, opts) do
        {:ok, results} ->
          interface_count = length(results)
          assert interface_count >= 0

        {:error, :timeout} ->
          :ok

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

    test "retrieve interface statistics" do
      target = "127.0.0.1"
      opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]

      # Common interface stats OIDs
      stats_oids = [
        "1.3.6.1.2.1.2.2.1.10.1",
        # ifInOctets for interface 1
        "1.3.6.1.2.1.2.2.1.16.1",
        # ifOutOctets for interface 1
        "1.3.6.1.2.1.2.2.1.8.1"
        # ifOperStatus for interface 1
      ]

      results =
        Enum.map(stats_oids, fn oid ->
          SnmpMgr.get(target, oid, opts)
        end)

      assert Enum.all?(results, fn result ->
               match?({:ok, _}, result) or match?({:error, _}, result)
             end)
    end
  end

  describe "SnmpMgr performance and reliability" do
    test "handles multiple concurrent requests" do
      target = "127.0.0.1"
      oid = "1.3.6.1.2.1.1.3.0"
      opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]

      tasks =
        for _ <- 1..5 do
          Task.async(fn -> SnmpMgr.get(target, oid, opts) end)
        end

      results = Task.await_many(tasks, 10_000)

      # All requests should complete
      assert length(results) == 5

      # All should either succeed or have consistent errors
      assert Enum.all?(results, fn result ->
               match?({:ok, _}, result) or match?({:error, _}, result)
             end)
    end

    test "respects timeout parameter" do
      target = "192.0.2.1"
      oid = "1.3.6.1.2.1.1.1.0"
      opts = [community: "public", version: :v2c, port: 161, timeout: 500]

      start_time = System.monotonic_time(:millisecond)
      result = SnmpMgr.get(target, oid, opts)
      end_time = System.monotonic_time(:millisecond)

      assert {:error, :timeout} = result

      # Should timeout around the specified timeout value
      duration = end_time - start_time
      # Allow some overhead
      assert duration < 2000
    end

    test "handles rapid sequential requests" do
      target = "127.0.0.1"
      oid = "1.3.6.1.2.1.1.3.0"
      opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]

      results =
        for _ <- 1..10 do
          SnmpMgr.get(target, oid, opts)
        end

      # All should complete
      assert length(results) == 10

      # All should have consistent results
      assert Enum.all?(results, fn result ->
               match?({:ok, _}, result) or match?({:error, _}, result)
             end)
    end
  end

  describe "SnmpMgr data type handling" do
    test "correctly handles timeticks values" do
      target = "127.0.0.1"
      oid = "1.3.6.1.2.1.1.3.0"
      # sysUpTime
      opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]

      case SnmpMgr.get(target, oid, opts) do
        {:ok, value} ->
          assert is_tuple(value)
          assert elem(value, 0) == :timeticks

        {:error, :timeout} ->
          :ok

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

    test "correctly handles octet string values" do
      target = "127.0.0.1"
      oid = "1.3.6.1.2.1.1.1.0"
      # sysDescr
      opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]

      case SnmpMgr.get(target, oid, opts) do
        {:ok, value} ->
          assert is_tuple(value)
          # Could be :octet_string
          type = elem(value, 0)
          assert is_atom(type)

        {:error, :timeout} ->
          :ok

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

    test "correctly handles integer values" do
      target = "127.0.0.1"
      oid = "1.3.6.1.2.1.2.2.1.8.1"
      # ifOperStatus
      opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]

      case SnmpMgr.get(target, oid, opts) do
        {:ok, value} ->
          assert is_tuple(value) or is_integer(value)

        {:error, :timeout} ->
          :ok

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