defmodule Towerops.Snmp.Profiles.Vendors.CambiumTest do
  use Towerops.DataCase, async: true

  import Mox

  alias Towerops.Snmp.Profiles.Vendors.Cambium
  alias Towerops.Snmp.SnmpMock

  setup :verify_on_exit!

  @client_opts [
    ip: "192.168.1.1",
    community: "public",
    version: "2c",
    port: 161,
    timeout: 5000
  ]

  describe "profile_names/0" do
    test "returns expected profile names" do
      assert Cambium.profile_names() == [
               "cmm",
               "cmm4",
               "cnpilote",
               "cnpilotr",
               "epmp",
               "pmp",
               "ptp250",
               "ptp500",
               "ptp600",
               "ptp650",
               "ptp670",
               "ptp800"
             ]
    end
  end

  describe "detect_hardware/1" do
    test "returns hardware description when SNMP responds" do
      expect(SnmpMock, :get, fn _, "1.3.6.1.2.1.1.1.0", _ ->
        {:ok, "Test Device Model XYZ"}
      end)

      assert Cambium.detect_hardware(@client_opts) == "Test Device Model XYZ"
    end

    test "returns nil when SNMP fails" do
      expect(SnmpMock, :get, fn _, _, _ ->
        {:error, :timeout}
      end)

      assert Cambium.detect_hardware(@client_opts) == nil
    end

    test "truncates long descriptions to 64 characters" do
      long_descr = String.duplicate("A", 100)

      expect(SnmpMock, :get, fn _, "1.3.6.1.2.1.1.1.0", _ ->
        {:ok, long_descr}
      end)

      result = Cambium.detect_hardware(@client_opts)
      assert String.length(result) == 64
    end
  end

  describe "wireless_oid_defs/0" do
    test "returns list of sensor definitions" do
      defs = Cambium.wireless_oid_defs()

      assert is_list(defs)
      assert [_ | _] = defs
    end

    test "each definition has required fields" do
      defs = Cambium.wireless_oid_defs()

      Enum.each(defs, fn def ->
        assert Map.has_key?(def, :oid)
        assert Map.has_key?(def, :sensor_descr)
        assert Map.has_key?(def, :sensor_type)
        assert Map.has_key?(def, :sensor_unit)
        assert Map.has_key?(def, :sensor_divisor)
      end)
    end
  end

  describe "discover_wireless_sensors/1 - static sensors" do
    test "discovers static sensors when SNMP responds" do
      defs = Cambium.wireless_oid_defs()
      num_defs = length(defs)

      # Expect mode check + static sensor calls
      stub(SnmpMock, :get, fn _, oid, _ ->
        case oid do
          # Mode check fails (not ePMP)
          "1.3.6.1.4.1.17713.21.1.2.32.0" ->
            {:error, :no_such_object}

          # Static CPU sensor
          _ ->
            {:ok, 42}
        end
      end)

      sensors = Cambium.discover_wireless_sensors(@client_opts)

      assert is_list(sensors)
      assert length(sensors) == num_defs
    end
  end

  describe "discover_wireless_sensors/1 - ePMP Access Point" do
    test "discovers all wireless sensors for ePMP AP mode" do
      # Mock ePMP detection and sensor values
      stub(SnmpMock, :get, fn _, oid, _ ->
        cond do
          # wirelessInterfaceMode (1 = AP mode)
          oid == "1.3.6.1.4.1.17713.21.1.2.32.0" ->
            {:ok, 1}

          # RSSI
          oid == "1.3.6.1.4.1.17713.21.1.2.3.0" ->
            {:ok, -65}

          # SNR
          oid == "1.3.6.1.4.1.17713.21.1.2.18.0" ->
            {:ok, 25}

          # Frequency
          oid == "1.3.6.1.4.1.17713.21.1.2.1.0" ->
            {:ok, 5180}

          # Client count (AP mode only)
          oid == "1.3.6.1.4.1.17713.21.1.2.10.0" ->
            {:ok, 12}

          # Static CPU sensor
          oid == "1.3.6.1.2.1.25.3.3.1.2.1" ->
            {:ok, 45}

          true ->
            {:error, :no_such_object}
        end
      end)

      sensors = Cambium.discover_wireless_sensors(@client_opts)

      # Should have: 1 static + 4 ePMP sensors (RSSI, SNR, Freq, Clients)
      assert length(sensors) == 5

      # Check RSSI sensor
      rssi_sensor = Enum.find(sensors, &(&1.sensor_type == "rssi"))
      assert rssi_sensor
      assert rssi_sensor.sensor_descr == "Downlink RSSI"
      assert rssi_sensor.last_value == -65
      assert rssi_sensor.sensor_unit == "dBm"

      # Check SNR sensor
      snr_sensor = Enum.find(sensors, &(&1.sensor_type == "snr"))
      assert snr_sensor
      assert snr_sensor.sensor_descr == "Downlink SNR"
      assert snr_sensor.last_value == 25
      assert snr_sensor.sensor_unit == "dB"

      # Check Frequency sensor
      freq_sensor = Enum.find(sensors, &(&1.sensor_type == "frequency"))
      assert freq_sensor
      assert freq_sensor.sensor_descr == "RF Frequency"
      assert freq_sensor.last_value == 5180
      assert freq_sensor.sensor_unit == "MHz"

      # Check Clients sensor (AP mode only)
      clients_sensor = Enum.find(sensors, &(&1.sensor_type == "clients"))
      assert clients_sensor
      assert clients_sensor.sensor_descr == "Connected Subscribers"
      assert clients_sensor.last_value == 12
    end
  end

  describe "discover_wireless_sensors/1 - ePMP Subscriber Module" do
    test "discovers wireless sensors for ePMP SM mode (no client count)" do
      # Mock wirelessInterfaceMode (2 = SM mode)
      stub(SnmpMock, :get, fn _, oid, _ ->
        cond do
          oid == "1.3.6.1.4.1.17713.21.1.2.32.0" ->
            {:ok, 2}

          # RSSI
          oid == "1.3.6.1.4.1.17713.21.1.2.3.0" ->
            {:ok, -72}

          # SNR
          oid == "1.3.6.1.4.1.17713.21.1.2.18.0" ->
            {:ok, 18}

          # Frequency
          oid == "1.3.6.1.4.1.17713.21.1.2.1.0" ->
            {:ok, 5220}

          # Static CPU sensor
          oid == "1.3.6.1.2.1.25.3.3.1.2.1" ->
            {:ok, 35}

          true ->
            {:error, :no_such_object}
        end
      end)

      sensors = Cambium.discover_wireless_sensors(@client_opts)

      # Should have: 1 static + 3 ePMP sensors (RSSI, SNR, Freq) - NO clients
      assert length(sensors) == 4

      # Verify no clients sensor in SM mode
      refute Enum.any?(sensors, &(&1.sensor_type == "clients"))

      # But should have RSSI, SNR, Frequency
      assert Enum.any?(sensors, &(&1.sensor_type == "rssi"))
      assert Enum.any?(sensors, &(&1.sensor_type == "snr"))
      assert Enum.any?(sensors, &(&1.sensor_type == "frequency"))
    end
  end

  describe "discover_wireless_sensors/1 - non-ePMP device" do
    test "returns only static sensors when device is not ePMP" do
      # Mock wirelessInterfaceMode check fails (not ePMP)
      stub(SnmpMock, :get, fn _, oid, _ ->
        case oid do
          "1.3.6.1.4.1.17713.21.1.2.32.0" ->
            {:error, :no_such_object}

          "1.3.6.1.2.1.25.3.3.1.2.1" ->
            {:ok, 40}

          _ ->
            {:error, :no_such_object}
        end
      end)

      sensors = Cambium.discover_wireless_sensors(@client_opts)

      # Should only have static CPU sensor
      assert length(sensors) == 1
      assert hd(sensors).sensor_type == "load"
    end
  end

  describe "discover_wireless_sensors/1 - sensor filtering" do
    test "filters out sensors that fail to respond" do
      # Mock some sensors succeeding and some failing
      stub(SnmpMock, :get, fn _, oid, _ ->
        cond do
          oid == "1.3.6.1.4.1.17713.21.1.2.32.0" ->
            {:ok, 1}

          # RSSI responds
          oid == "1.3.6.1.4.1.17713.21.1.2.3.0" ->
            {:ok, -68}

          # SNR fails
          oid == "1.3.6.1.4.1.17713.21.1.2.18.0" ->
            {:error, :no_such_object}

          # Frequency responds
          oid == "1.3.6.1.4.1.17713.21.1.2.1.0" ->
            {:ok, 5200}

          # Clients fails
          oid == "1.3.6.1.4.1.17713.21.1.2.10.0" ->
            {:error, :no_such_object}

          # Static CPU sensor
          oid == "1.3.6.1.2.1.25.3.3.1.2.1" ->
            {:ok, 50}

          true ->
            {:error, :no_such_object}
        end
      end)

      sensors = Cambium.discover_wireless_sensors(@client_opts)

      # Should have: 1 static + 2 ePMP sensors (RSSI, Freq)
      assert length(sensors) == 3

      # Should have RSSI and Frequency
      assert Enum.any?(sensors, &(&1.sensor_type == "rssi" && &1.last_value == -68))
      assert Enum.any?(sensors, &(&1.sensor_type == "frequency" && &1.last_value == 5200))

      # Should NOT have SNR or clients
      refute Enum.any?(sensors, &(&1.sensor_type == "snr"))
      refute Enum.any?(sensors, &(&1.sensor_type == "clients"))
    end
  end

  describe "discover_wireless_sensors/1 - edge cases" do
    test "handles invalid mode value gracefully" do
      # Mock mode returns invalid value (not 1 or 2)
      stub(SnmpMock, :get, fn _, oid, _ ->
        case oid do
          "1.3.6.1.4.1.17713.21.1.2.32.0" ->
            {:ok, 99}

          "1.3.6.1.2.1.25.3.3.1.2.1" ->
            {:ok, 42}

          _ ->
            {:error, :no_such_object}
        end
      end)

      sensors = Cambium.discover_wireless_sensors(@client_opts)

      # Should only have static sensor (invalid mode)
      assert length(sensors) == 1
      assert hd(sensors).sensor_type == "load"
    end

    test "returns empty list when all sensors fail" do
      stub(SnmpMock, :get, fn _, _, _ ->
        {:error, :timeout}
      end)

      sensors = Cambium.discover_wireless_sensors(@client_opts)

      assert sensors == []
    end
  end
end
