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

  import Mox

  alias Towerops.Snmp.Profiles.Vendors.Unifi
  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 unifi and unifi-usp profile names" do
      assert Unifi.profile_names() == ["unifi", "unifi-usp"]
    end
  end

  describe "detect_hardware/1" do
    test "detects UniFi model from system model OID" do
      expect(SnmpMock, :get, fn _, "1.3.6.1.4.1.41112.1.6.3.3.0", _ ->
        {:ok, {:octet_string, "UAP-AC-HD"}}
      end)

      assert Unifi.detect_hardware(@client_opts) == "UAP-AC-HD"
    end

    test "returns nil when model unavailable" do
      expect(SnmpMock, :get, fn _, "1.3.6.1.4.1.41112.1.6.3.3.0", _ ->
        {:error, :no_such_object}
      end)

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

    test "returns nil when model is empty string" do
      expect(SnmpMock, :get, fn _, "1.3.6.1.4.1.41112.1.6.3.3.0", _ ->
        {:ok, {:octet_string, ""}}
      end)

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

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

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

    test "includes client count sensors for both radios" do
      defs = Unifi.wireless_oid_defs()
      client_sensors = Enum.filter(defs, &(&1.sensor_type == "clients"))

      assert length(client_sensors) == 2
      # ng = 2.4GHz, na = 5GHz
      assert Enum.any?(client_sensors, &String.contains?(&1.sensor_descr, "ng"))
      assert Enum.any?(client_sensors, &String.contains?(&1.sensor_descr, "na"))
    end

    test "includes CCQ sensors with correct divisor" do
      defs = Unifi.wireless_oid_defs()
      ccq_sensors = Enum.filter(defs, &(&1.sensor_type == "ccq"))

      assert length(ccq_sensors) == 2
      # CCQ values are divided by 10 for percentage
      assert Enum.all?(ccq_sensors, &(&1.sensor_divisor == 10))
    end

    test "includes power sensors" do
      defs = Unifi.wireless_oid_defs()
      power_sensors = Enum.filter(defs, &(&1.sensor_type == "power"))

      assert length(power_sensors) == 2
    end

    test "includes utilization sensors" do
      defs = Unifi.wireless_oid_defs()
      util_sensors = Enum.filter(defs, &(&1.sensor_type == "utilization"))

      assert length(util_sensors) >= 2
    end
  end

  describe "discover_wireless_sensors/1" do
    test "discovers sensors when SNMP responds" do
      # Mock static sensor OIDs
      expect(SnmpMock, :get, 10, fn _, oid, _ ->
        cond do
          String.contains?(oid, "41112.1.6.1.2.1.8.1") -> {:ok, {:integer, 25}}
          String.contains?(oid, "41112.1.6.1.2.1.8.2") -> {:ok, {:integer, 15}}
          String.contains?(oid, "41112.1.6.1.2.1.3.1") -> {:ok, {:integer, 980}}
          String.contains?(oid, "41112.1.6.1.2.1.3.2") -> {:ok, {:integer, 950}}
          String.contains?(oid, "41112.1.6.1.2.1.21.1") -> {:ok, {:integer, 20}}
          String.contains?(oid, "41112.1.6.1.2.1.21.2") -> {:ok, {:integer, 23}}
          String.contains?(oid, "41112.1.6.1.1.1.6.1") -> {:ok, {:integer, 45}}
          String.contains?(oid, "41112.1.6.1.1.1.6.2") -> {:ok, {:integer, 30}}
          String.contains?(oid, "41112.1.6.1.1.1.7.1") -> {:ok, {:integer, 15}}
          String.contains?(oid, "41112.1.6.1.1.1.8.1") -> {:ok, {:integer, 10}}
          true -> {:error, :no_such_object}
        end
      end)

      # Mock channel walk for frequency sensors
      expect(SnmpMock, :walk, 2, fn _, oid, _ ->
        cond do
          String.ends_with?(oid, "1.6.1.2.1.4") ->
            {:ok,
             [
               %{oid: "1.3.6.1.4.1.41112.1.6.1.2.1.4.1", value: {:integer, 6}},
               %{oid: "1.3.6.1.4.1.41112.1.6.1.2.1.4.2", value: {:integer, 36}}
             ]}

          String.ends_with?(oid, "1.6.1.2.1.1") ->
            {:ok,
             [
               %{oid: "1.3.6.1.4.1.41112.1.6.1.2.1.1.1", value: {:octet_string, "ng"}},
               %{oid: "1.3.6.1.4.1.41112.1.6.1.2.1.1.2", value: {:octet_string, "na"}}
             ]}

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

      sensors = Unifi.discover_wireless_sensors(@client_opts)

      assert is_list(sensors)
      # 10 static sensors + 2 frequency sensors
      assert length(sensors) == 12

      # Check frequency sensors were added
      freq_sensors = Enum.filter(sensors, &(&1.sensor_type == "frequency"))
      assert length(freq_sensors) == 2

      # Verify channel 6 (2.4GHz) converted to 2437 MHz
      ng_freq = Enum.find(freq_sensors, &String.contains?(&1.sensor_descr, "ng"))
      assert ng_freq.last_value == 2437
      assert ng_freq.sensor_unit == "MHz"

      # Verify channel 36 (5GHz) converted to 5180 MHz
      na_freq = Enum.find(freq_sensors, &String.contains?(&1.sensor_descr, "na"))
      assert na_freq.last_value == 5180
    end

    test "returns only static sensors when channel walk fails" do
      # Mock static sensor OIDs
      expect(SnmpMock, :get, 10, fn _, oid, _ ->
        cond do
          String.contains?(oid, "41112.1.6.1.2.1.8.1") -> {:ok, {:integer, 25}}
          String.contains?(oid, "41112.1.6.1.2.1.8.2") -> {:ok, {:integer, 15}}
          String.contains?(oid, "41112.1.6.1.2.1.3.1") -> {:ok, {:integer, 980}}
          String.contains?(oid, "41112.1.6.1.2.1.3.2") -> {:ok, {:integer, 950}}
          String.contains?(oid, "41112.1.6.1.2.1.21.1") -> {:ok, {:integer, 20}}
          String.contains?(oid, "41112.1.6.1.2.1.21.2") -> {:ok, {:integer, 23}}
          String.contains?(oid, "41112.1.6.1.1.1.6.1") -> {:ok, {:integer, 45}}
          String.contains?(oid, "41112.1.6.1.1.1.6.2") -> {:ok, {:integer, 30}}
          String.contains?(oid, "41112.1.6.1.1.1.7.1") -> {:ok, {:integer, 15}}
          String.contains?(oid, "41112.1.6.1.1.1.8.1") -> {:ok, {:integer, 10}}
          true -> {:error, :no_such_object}
        end
      end)

      # Mock channel walk failure (returns empty list)
      expect(SnmpMock, :walk, fn _, _, _ ->
        {:ok, []}
      end)

      sensors = Unifi.discover_wireless_sensors(@client_opts)

      assert is_list(sensors)
      # Only static sensors, no frequency sensors
      assert length(sensors) == 10
      assert Enum.all?(sensors, &(&1.sensor_type != "frequency"))
    end

    test "filters out unknown channel numbers (returns 0 frequency)" do
      expect(SnmpMock, :get, 10, fn _, _, _ -> {:error, :no_such_object} end)

      # Mock channel walk with unknown channel
      expect(SnmpMock, :walk, 2, fn _, oid, _ ->
        cond do
          String.ends_with?(oid, "1.6.1.2.1.4") ->
            {:ok,
             [
               %{oid: "1.3.6.1.4.1.41112.1.6.1.2.1.4.1", value: {:integer, 6}},
               %{oid: "1.3.6.1.4.1.41112.1.6.1.2.1.4.2", value: {:integer, 999}}
             ]}

          String.ends_with?(oid, "1.6.1.2.1.1") ->
            {:ok,
             [
               %{oid: "1.3.6.1.4.1.41112.1.6.1.2.1.1.1", value: {:octet_string, "ng"}},
               %{oid: "1.3.6.1.4.1.41112.1.6.1.2.1.1.2", value: {:octet_string, "na"}}
             ]}

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

      sensors = Unifi.discover_wireless_sensors(@client_opts)

      # Only valid channel 6 should be included (channel 999 filtered out)
      freq_sensors = Enum.filter(sensors, &(&1.sensor_type == "frequency"))
      assert length(freq_sensors) == 1
      assert hd(freq_sensors).last_value == 2437
    end

    test "returns empty list when no sensors respond" do
      expect(SnmpMock, :get, 10, fn _, _, _ ->
        {:error, :no_such_object}
      end)

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

      sensors = Unifi.discover_wireless_sensors(@client_opts)

      assert sensors == []
    end
  end

  describe "channel_to_frequency/1" do
    test "converts 2.4 GHz channels correctly" do
      # Access private function via module's public function for testing
      # Channel 1 = 2412 MHz, Channel 6 = 2437 MHz, Channel 11 = 2462 MHz
      assert convert_channel(1) == 2412
      assert convert_channel(6) == 2437
      assert convert_channel(11) == 2462
    end

    test "converts 5 GHz channels correctly" do
      # Channel 36 = 5180 MHz, Channel 149 = 5745 MHz
      assert convert_channel(36) == 5180
      assert convert_channel(149) == 5745
      assert convert_channel(165) == 5825
    end

    test "returns 0 for unknown channels" do
      assert convert_channel(999) == 0
      assert convert_channel(0) == 0
      assert convert_channel(-1) == 0
    end

    # Helper to test private channel_to_frequency function
    defp convert_channel(channel) do
      # Create a mock client that returns the channel
      expect(SnmpMock, :get, 10, fn _, _, _ -> {:error, :no_such_object} end)

      expect(SnmpMock, :walk, 2, fn _, oid, _ ->
        cond do
          String.ends_with?(oid, "1.6.1.2.1.4") ->
            {:ok, [%{oid: "1.3.6.1.4.1.41112.1.6.1.2.1.4.1", value: {:integer, channel}}]}

          String.ends_with?(oid, "1.6.1.2.1.1") ->
            {:ok, [%{oid: "1.3.6.1.4.1.41112.1.6.1.2.1.1.1", value: {:octet_string, "test"}}]}

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

      sensors = Unifi.discover_wireless_sensors(@client_opts)
      freq_sensors = Enum.filter(sensors, &(&1.sensor_type == "frequency"))

      case freq_sensors do
        [sensor | _] -> sensor.last_value
        [] -> 0
      end
    end
  end
end
