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

  import Mox

  alias Towerops.Snmp.Profiles.Vendors.Mikrotik
  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 Mikrotik.profile_names() == ["swos"]
    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 Mikrotik.detect_hardware(@client_opts) == "Test Device Model XYZ"
    end

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

      assert Mikrotik.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 = Mikrotik.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 = Mikrotik.wireless_oid_defs()

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

    test "each definition has required fields" do
      defs = Mikrotik.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" do
    test "discovers AP sensors from mtxrWlApTable" do
      expect(SnmpMock, :get, fn _, _oid, _ ->
        {:ok, 42}
      end)

      expect(SnmpMock, :walk, 5, fn _, oid, _ ->
        case oid do
          "1.3.6.1.4.1.14988.1.1.1.3" ->
            {:ok,
             [
               %{oid: "1.3.6.1.4.1.14988.1.1.1.3.1.4.1", value: {:octet_string, "TestAP"}},
               %{oid: "1.3.6.1.4.1.14988.1.1.1.3.1.7.1", value: {:integer, 2437}},
               %{oid: "1.3.6.1.4.1.14988.1.1.1.3.1.6.1", value: {:integer, 5}},
               %{oid: "1.3.6.1.4.1.14988.1.1.1.3.1.10.1", value: {:integer, 85}},
               %{oid: "1.3.6.1.4.1.14988.1.1.1.3.1.2.1", value: {:integer, 54_000_000}},
               %{oid: "1.3.6.1.4.1.14988.1.1.1.3.1.3.1", value: {:integer, 48_000_000}},
               %{oid: "1.3.6.1.4.1.14988.1.1.1.3.1.9.1", value: {:integer, -95}}
             ]}

          _ ->
            {:ok, []}
        end
      end)

      sensors = Mikrotik.discover_wireless_sensors(@client_opts)

      # Should have: 1 static + 1 client + 1 CCQ + 1 freq + 1 noise + 2 rate (TX/RX) = 7 sensors
      assert length(sensors) == 7

      # Check clients sensor
      clients_sensor = Enum.find(sensors, &(&1.sensor_type == "clients"))
      assert clients_sensor
      assert clients_sensor.sensor_descr == "2G: TestAP"
      assert clients_sensor.last_value == 5

      # Check CCQ sensor
      ccq_sensor = Enum.find(sensors, &(&1.sensor_type == "ccq"))
      assert ccq_sensor
      assert ccq_sensor.last_value == 85

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

      # Check noise floor sensor
      noise_sensor = Enum.find(sensors, &(&1.sensor_type == "noise-floor"))
      assert noise_sensor
      assert noise_sensor.last_value == -95
      assert noise_sensor.sensor_unit == "dBm"

      # Check rate sensors
      rate_sensors = Enum.filter(sensors, &(&1.sensor_type == "rate"))
      assert length(rate_sensors) == 2
      assert Enum.any?(rate_sensors, &String.contains?(&1.sensor_descr, "Tx"))
      assert Enum.any?(rate_sensors, &String.contains?(&1.sensor_descr, "Rx"))
    end

    test "discovers station sensors from mtxrWlStatTable" do
      expect(SnmpMock, :get, fn _, _oid, _ ->
        {:ok, 42}
      end)

      expect(SnmpMock, :walk, 5, fn _, oid, _ ->
        case oid do
          "1.3.6.1.4.1.14988.1.1.1.1" ->
            {:ok,
             [
               %{oid: "1.3.6.1.4.1.14988.1.1.1.1.1.5.1", value: {:octet_string, "TestSta"}},
               %{oid: "1.3.6.1.4.1.14988.1.1.1.1.1.7.1", value: {:integer, 5180}},
               %{oid: "1.3.6.1.4.1.14988.1.1.1.1.1.9.1", value: {:integer, 90}},
               %{oid: "1.3.6.1.4.1.14988.1.1.1.1.1.10.1", value: {:integer, 88}},
               %{oid: "1.3.6.1.4.1.14988.1.1.1.1.1.2.1", value: {:integer, 300_000_000}},
               %{oid: "1.3.6.1.4.1.14988.1.1.1.1.1.3.1", value: {:integer, 280_000_000}}
             ]}

          _ ->
            {:ok, []}
        end
      end)

      sensors = Mikrotik.discover_wireless_sensors(@client_opts)

      # Should have: 1 static + 2 CCQ (TX/RX) + 1 freq + 2 rate (TX/RX) = 6 sensors
      assert length(sensors) == 6

      # Check CCQ sensors (TX and RX separate)
      ccq_sensors = Enum.filter(sensors, &(&1.sensor_type == "ccq"))
      assert length(ccq_sensors) == 2
      assert Enum.any?(ccq_sensors, &String.contains?(&1.sensor_descr, "Tx"))
      assert Enum.any?(ccq_sensors, &String.contains?(&1.sensor_descr, "Rx"))

      tx_ccq = Enum.find(ccq_sensors, &String.contains?(&1.sensor_descr, "Tx"))
      assert tx_ccq.last_value == 90

      rx_ccq = Enum.find(ccq_sensors, &String.contains?(&1.sensor_descr, "Rx"))
      assert rx_ccq.last_value == 88
    end

    test "discovers 60GHz sensors from mtxrWl60GTable" do
      expect(SnmpMock, :get, fn _, _oid, _ ->
        {:ok, 42}
      end)

      expect(SnmpMock, :walk, 5, fn _, oid, _ ->
        case oid do
          "1.3.6.1.4.1.14988.1.1.1.8" ->
            {:ok,
             [
               %{oid: "1.3.6.1.4.1.14988.1.1.1.8.1.7.1", value: {:octet_string, "60GLink"}},
               %{oid: "1.3.6.1.4.1.14988.1.1.1.8.1.6.1", value: {:integer, 60_480}},
               %{oid: "1.3.6.1.4.1.14988.1.1.1.8.1.12.1", value: {:integer, -55}},
               %{oid: "1.3.6.1.4.1.14988.1.1.1.8.1.8.1", value: {:integer, 75}},
               %{oid: "1.3.6.1.4.1.14988.1.1.1.8.1.13.1", value: {:integer, 1_800_000_000}}
             ]}

          _ ->
            {:ok, []}
        end
      end)

      sensors = Mikrotik.discover_wireless_sensors(@client_opts)

      # Should have: 1 static + 1 freq + 1 RSSI + 1 quality + 1 rate = 5 sensors
      assert length(sensors) == 5

      # Check 60GHz frequency
      freq_sensor = Enum.find(sensors, &(&1.sensor_type == "frequency"))
      assert freq_sensor
      assert freq_sensor.sensor_descr == "60G: 60GLink"
      assert freq_sensor.last_value == 60_480

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

      # Check quality
      quality_sensor = Enum.find(sensors, &(&1.sensor_type == "quality"))
      assert quality_sensor
      assert quality_sensor.last_value == 75

      # Check PHY rate with divisor
      rate_sensor = Enum.find(sensors, &(&1.sensor_type == "rate"))
      assert rate_sensor
      assert rate_sensor.sensor_divisor == 1_000_000
      assert rate_sensor.last_value == 1_800_000_000
    end

    test "discovers 60GHz distance sensors from mtxrWl60GStaTable" do
      expect(SnmpMock, :get, fn _, _oid, _ ->
        {:ok, 42}
      end)

      expect(SnmpMock, :walk, 5, fn _, oid, _ ->
        case oid do
          "1.3.6.1.4.1.14988.1.1.1.9" ->
            {:ok,
             [
               %{oid: "1.3.6.1.4.1.14988.1.1.1.9.1.4.1", value: {:octet_string, "RemoteSta"}},
               %{oid: "1.3.6.1.4.1.14988.1.1.1.9.1.10.1", value: {:integer, 5_000_000}}
             ]}

          _ ->
            {:ok, []}
        end
      end)

      sensors = Mikrotik.discover_wireless_sensors(@client_opts)

      # Should have: 1 static + 1 distance = 2 sensors
      assert length(sensors) == 2

      # Check distance sensor
      distance_sensor = Enum.find(sensors, &(&1.sensor_type == "distance"))
      assert distance_sensor
      assert distance_sensor.sensor_descr == "60G: Sta > RemoteSta"
      assert distance_sensor.last_value == 5_000_000
      assert distance_sensor.sensor_divisor == 100_000
      assert distance_sensor.sensor_unit == "km"
    end

    test "discovers LTE sensors from mtxrLTEModemTable" do
      expect(SnmpMock, :get, fn _, _oid, _ ->
        {:ok, 42}
      end)

      expect(SnmpMock, :walk, 5, fn _, oid, _ ->
        case oid do
          "1.3.6.1.4.1.14988.1.1.16.1" ->
            {:ok,
             [
               %{oid: "1.3.6.1.4.1.14988.1.1.16.1.1.3.1", value: {:integer, -10}},
               %{oid: "1.3.6.1.4.1.14988.1.1.16.1.1.4.1", value: {:integer, -80}},
               %{oid: "1.3.6.1.4.1.14988.1.1.16.1.1.7.1", value: {:integer, 13}}
             ]}

          _ ->
            {:ok, []}
        end
      end)

      sensors = Mikrotik.discover_wireless_sensors(@client_opts)

      # Should have: 1 static + 1 RSRQ + 1 RSRP + 1 SINR = 4 sensors
      assert length(sensors) == 4

      # Check RSRQ
      rsrq_sensor = Enum.find(sensors, &(&1.sensor_type == "rsrq"))
      assert rsrq_sensor
      assert rsrq_sensor.sensor_descr == "LTE: Signal RSRQ"
      assert rsrq_sensor.last_value == -10
      assert rsrq_sensor.sensor_unit == "dB"

      # Check RSRP
      rsrp_sensor = Enum.find(sensors, &(&1.sensor_type == "rsrp"))
      assert rsrp_sensor
      assert rsrp_sensor.sensor_descr == "LTE: Signal RSRP"
      assert rsrp_sensor.last_value == -80
      assert rsrp_sensor.sensor_unit == "dBm"

      # Check SINR
      sinr_sensor = Enum.find(sensors, &(&1.sensor_type == "sinr"))
      assert sinr_sensor
      assert sinr_sensor.sensor_descr == "LTE: Signal SINR"
      assert sinr_sensor.last_value == 13
      assert sinr_sensor.sensor_unit == "dB"
    end

    test "skips AP sensors with no data" do
      expect(SnmpMock, :get, fn _, _oid, _ ->
        {:ok, 42}
      end)

      expect(SnmpMock, :walk, 5, fn _, oid, _ ->
        case oid do
          "1.3.6.1.4.1.14988.1.1.1.3" ->
            {:ok,
             [
               %{oid: "1.3.6.1.4.1.14988.1.1.1.3.1.4.1", value: {:octet_string, "TestAP"}},
               %{oid: "1.3.6.1.4.1.14988.1.1.1.3.1.7.1", value: {:integer, 0}},
               %{oid: "1.3.6.1.4.1.14988.1.1.1.3.1.6.1", value: {:integer, 1}},
               %{oid: "1.3.6.1.4.1.14988.1.1.1.3.1.10.1", value: {:integer, 0}},
               %{oid: "1.3.6.1.4.1.14988.1.1.1.3.1.2.1", value: {:integer, 0}},
               %{oid: "1.3.6.1.4.1.14988.1.1.1.3.1.3.1", value: {:integer, 0}}
             ]}

          _ ->
            {:ok, []}
        end
      end)

      sensors = Mikrotik.discover_wireless_sensors(@client_opts)

      # Should skip: frequency (0), CCQ (no clients), TX/RX rate (both 0)
      # Should have: 1 static + 1 client + 0 noise floor (no data) = 2 sensors
      assert length(sensors) == 2

      # Verify we only have clients sensor, no CCQ/freq/rate
      assert Enum.any?(sensors, &(&1.sensor_type == "clients"))
      refute Enum.any?(sensors, &(&1.sensor_type == "ccq"))
      refute Enum.any?(sensors, &(&1.sensor_type == "frequency"))
      refute Enum.any?(sensors, &(&1.sensor_type == "rate"))
    end

    test "returns only static sensors when no wireless tables respond" do
      expect(SnmpMock, :get, fn _, _oid, _ ->
        {:ok, 42}
      end)

      expect(SnmpMock, :walk, 5, fn _, _, _ ->
        {:ok, []}
      end)

      sensors = Mikrotik.discover_wireless_sensors(@client_opts)

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