defmodule Towerops.Snmp.DeferredDiscoveryTest do
  use ExUnit.Case, async: true

  import Mox

  alias Towerops.Snmp.DeferredDiscovery
  alias Towerops.Snmp.SnmpMock

  setup :verify_on_exit!

  describe "fast_check/3" do
    test "returns result when check completes within timeout" do
      client_opts = %{host: "192.168.1.1"}

      result =
        DeferredDiscovery.fast_check(client_opts, fn ->
          {:ok, "test result"}
        end)

      assert result == {:ok, "test result"}
    end

    test "returns error when check times out" do
      client_opts = %{host: "192.168.1.1"}

      result =
        DeferredDiscovery.fast_check(
          client_opts,
          fn ->
            Process.sleep(500)
            {:ok, "too slow"}
          end,
          timeout: 50
        )

      assert result == {:error, :timeout}
    end

    test "returns error result from check function" do
      client_opts = %{host: "192.168.1.1"}

      result =
        DeferredDiscovery.fast_check(client_opts, fn ->
          {:error, :connection_failed}
        end)

      assert result == {:error, :connection_failed}
    end
  end

  describe "slow_check/3" do
    test "returns result when check completes" do
      client_opts = %{host: "192.168.1.1"}

      result =
        DeferredDiscovery.slow_check(client_opts, fn ->
          {:ok, ["sensor1", "sensor2"]}
        end)

      assert result == {:ok, ["sensor1", "sensor2"]}
    end

    test "returns default value when check times out" do
      client_opts = %{host: "192.168.1.1"}

      result =
        DeferredDiscovery.slow_check(
          client_opts,
          fn ->
            Process.sleep(500)
            {:ok, "too slow"}
          end,
          timeout: 50,
          default: []
        )

      assert result == {:ok, []}
    end

    test "uses custom default value on timeout" do
      client_opts = %{host: "192.168.1.1"}

      result =
        DeferredDiscovery.slow_check(
          client_opts,
          fn ->
            Process.sleep(500)
            {:ok, "too slow"}
          end,
          timeout: 50,
          default: %{status: :timeout}
        )

      assert result == {:ok, %{status: :timeout}}
    end
  end

  describe "deferred_check/3" do
    test "returns ok result when check succeeds" do
      client_opts = %{host: "192.168.1.1"}

      result =
        DeferredDiscovery.deferred_check(client_opts, fn ->
          {:ok, ["neighbor1", "neighbor2"]}
        end)

      assert result == {:ok, ["neighbor1", "neighbor2"]}
    end

    test "returns deferred status when check fails" do
      client_opts = %{host: "192.168.1.1"}

      result =
        DeferredDiscovery.deferred_check(
          client_opts,
          fn ->
            {:error, :not_supported}
          end,
          name: "neighbors"
        )

      assert {:deferred, [], :not_supported} = result
    end

    test "returns timeout status when check times out" do
      client_opts = %{host: "192.168.1.1"}

      result =
        DeferredDiscovery.deferred_check(
          client_opts,
          fn ->
            Process.sleep(500)
            {:ok, "too slow"}
          end,
          timeout: 50,
          name: "neighbors"
        )

      assert {:timeout, []} = result
    end
  end

  describe "parallel_checks/1" do
    test "runs multiple checks in parallel" do
      checks = [
        {:sensors, fn -> {:ok, ["temp1", "temp2"]} end, []},
        {:interfaces, fn -> {:ok, ["eth0", "eth1"]} end, []},
        {:neighbors, fn -> {:ok, ["switch1"]} end, []}
      ]

      results = DeferredDiscovery.parallel_checks(checks)

      assert map_size(results) == 3
      assert {:ok, ["temp1", "temp2"]} = results[:sensors]
      assert {:ok, ["eth0", "eth1"]} = results[:interfaces]
      assert {:ok, ["switch1"]} = results[:neighbors]
    end

    test "handles mixed success and failure results" do
      checks = [
        {:sensors, fn -> {:ok, ["temp1"]} end, []},
        {:interfaces, fn -> {:error, :walk_failed} end, []},
        {:neighbors, fn -> {:ok, []} end, []}
      ]

      results = DeferredDiscovery.parallel_checks(checks)

      assert {:ok, ["temp1"]} = results[:sensors]
      assert {:error, :walk_failed} = results[:interfaces]
      assert {:ok, []} = results[:neighbors]
    end

    test "handles timeout with default value" do
      checks = [
        {:fast_check, fn -> {:ok, "fast"} end, [timeout: 1000]},
        {:slow_check,
         fn ->
           Process.sleep(500)
           {:ok, "slow"}
         end, [timeout: 50, default: []]}
      ]

      results = DeferredDiscovery.parallel_checks(checks)

      assert {:ok, "fast"} = results[:fast_check]
      assert {:timeout, []} = results[:slow_check]
    end

    test "handles exceptions gracefully" do
      checks = [
        {:good_check, fn -> {:ok, "good"} end, []},
        {:bad_check, fn -> raise "something went wrong" end, []}
      ]

      results = DeferredDiscovery.parallel_checks(checks)

      assert {:ok, "good"} = results[:good_check]
      assert {:error, "something went wrong"} = results[:bad_check]
    end

    test "handles empty list" do
      results = DeferredDiscovery.parallel_checks([])
      assert results == %{}
    end
  end

  describe "device_responsive?/2" do
    test "returns true when device responds" do
      expect(SnmpMock, :get, fn _target, _oid, _opts ->
        {:ok, [1, 3, 6, 1, 4, 1, 9]}
      end)

      client_opts = [ip: "192.168.1.1", community: "public", version: "2c"]

      assert DeferredDiscovery.device_responsive?(client_opts) == true
    end

    test "returns false when device does not respond" do
      expect(SnmpMock, :get, fn _target, _oid, _opts ->
        {:error, :timeout}
      end)

      client_opts = [ip: "192.168.1.1", community: "public", version: "2c"]

      assert DeferredDiscovery.device_responsive?(client_opts) == false
    end

    test "returns false when check times out" do
      expect(SnmpMock, :get, fn _target, _oid, _opts ->
        Process.sleep(500)
        {:ok, [1, 3, 6, 1, 4, 1, 9]}
      end)

      client_opts = [ip: "192.168.1.1", community: "public", version: "2c"]

      assert DeferredDiscovery.device_responsive?(client_opts, timeout: 50) == false
    end
  end

  describe "categorize_device_speed/2" do
    test "returns :fast for quick responses" do
      expect(SnmpMock, :get, fn _target, _oid, _opts ->
        {:ok, "Fast Device"}
      end)

      client_opts = [ip: "192.168.1.1", community: "public", version: "2c"]

      assert DeferredDiscovery.categorize_device_speed(client_opts) == :fast
    end

    test "returns :slow for delayed responses" do
      expect(SnmpMock, :get, fn _target, _oid, _opts ->
        # Sleep just past the threshold to be classified as slow
        Process.sleep(60)
        {:ok, "Slow Device"}
      end)

      client_opts = [ip: "192.168.1.1", community: "public", version: "2c"]

      # Use a shorter threshold (50ms) so 60ms delay = slow
      assert DeferredDiscovery.categorize_device_speed(client_opts, fast_threshold: 50) == :slow
    end

    test "returns :unresponsive when device errors" do
      expect(SnmpMock, :get, fn _target, _oid, _opts ->
        {:error, :timeout}
      end)

      client_opts = [ip: "192.168.1.1", community: "public", version: "2c"]

      assert DeferredDiscovery.categorize_device_speed(client_opts) == :unresponsive
    end
  end

  describe "timeouts_for_speed/1" do
    test "returns short timeouts for fast devices" do
      timeouts = DeferredDiscovery.timeouts_for_speed(:fast)

      assert timeouts[:fast] == 5_000
      assert timeouts[:slow] == 15_000
      assert timeouts[:deferred] == 30_000
    end

    test "returns medium timeouts for slow devices" do
      timeouts = DeferredDiscovery.timeouts_for_speed(:slow)

      assert timeouts[:fast] == 15_000
      assert timeouts[:slow] == 45_000
      assert timeouts[:deferred] == 90_000
    end

    test "returns long timeouts for unresponsive devices" do
      timeouts = DeferredDiscovery.timeouts_for_speed(:unresponsive)

      assert timeouts[:fast] == 30_000
      assert timeouts[:slow] == 60_000
      assert timeouts[:deferred] == 120_000
    end
  end
end
