defmodule Towerops.Snmp.Profiles.Vendors.Unifi do
  @moduledoc """
  Ubiquiti UniFi device-specific SNMP handling.

  Mirrors LibreNMS/OS/Unifi.php for wireless sensor discovery.

  Supported devices:
  - UniFi Access Points (UAP, UAP-AC, UAP-HD, etc.)
  - UniFi switches with wireless capabilities
  - UniFi SmartPower (USP) devices
  """

  @behaviour Towerops.Snmp.Profiles.Vendors.Vendor

  alias Towerops.Snmp.Client
  alias Towerops.Snmp.Profiles.Vendors.Vendor

  # OIDs for hardware detection
  @system_model "1.3.6.1.4.1.41112.1.6.3.3.0"

  @impl true
  def profile_names, do: ["unifi", "unifi-usp"]

  @impl true
  def detect_hardware(client_opts) do
    case Client.get(client_opts, @system_model) do
      {:ok, model} when is_binary(model) and model != "" ->
        model

      _ ->
        nil
    end
  end

  @impl true
  def discover_wireless_sensors(client_opts) do
    # Fetch static sensors from OID definitions
    static_sensors = Vendor.fetch_sensors(wireless_oid_defs(), client_opts)

    # Add dynamic frequency sensors (channel-to-frequency conversion)
    frequency_sensors = discover_frequency_sensors(client_opts)

    static_sensors ++ frequency_sensors
  end

  # Discover frequency sensors with dynamic channel-to-frequency conversion
  @spec discover_frequency_sensors(Client.connection_opts()) :: [map()]
  defp discover_frequency_sensors(client_opts) do
    with {:ok, channels} <- fetch_channels(client_opts),
         false <- Enum.empty?(channels) do
      radio_names = fetch_radio_names(client_opts)
      build_frequency_sensors(channels, radio_names)
    else
      _ -> []
    end
  end

  @spec fetch_channels(Client.connection_opts()) :: {:ok, map()} | {:error, term()}
  defp fetch_channels(client_opts) do
    Client.walk(client_opts, "1.3.6.1.4.1.41112.1.6.1.2.1.4")
  end

  @spec fetch_radio_names(Client.connection_opts()) :: map()
  defp fetch_radio_names(client_opts) do
    case Client.walk(client_opts, "1.3.6.1.4.1.41112.1.6.1.2.1.1") do
      {:ok, radios} when is_map(radios) ->
        Map.new(radios, fn {oid, name} ->
          index = oid |> String.split(".") |> List.last()
          {index, name}
        end)

      _ ->
        %{}
    end
  end

  @spec build_frequency_sensors(map(), map()) :: [map()]
  defp build_frequency_sensors(channels, radio_names) do
    channels
    |> Enum.map(fn {oid, channel} ->
      index = oid |> String.split(".") |> List.last()
      frequency = channel_to_frequency(channel)
      radio_name = Map.get(radio_names, index, "Radio #{index}")

      %{
        oid: oid,
        sensor_type: "frequency",
        sensor_descr: "Frequency (#{radio_name})",
        sensor_unit: "MHz",
        sensor_divisor: 1,
        sensor_index: "unifi_freq_#{index}",
        last_value: frequency
      }
    end)
    |> Enum.reject(&(&1.last_value == 0))
  end

  # Wi-Fi channel to frequency lookup table
  # Based on LibreNMS Wireless::channelToFrequency()
  @channel_frequency_map %{
    # 2.4 GHz band
    1 => 2412,
    2 => 2417,
    3 => 2422,
    4 => 2427,
    5 => 2432,
    6 => 2437,
    7 => 2442,
    8 => 2447,
    9 => 2452,
    10 => 2457,
    11 => 2462,
    12 => 2467,
    13 => 2472,
    14 => 2484,
    # 5 GHz band
    34 => 5170,
    36 => 5180,
    38 => 5190,
    40 => 5200,
    42 => 5210,
    44 => 5220,
    46 => 5230,
    48 => 5240,
    52 => 5260,
    56 => 5280,
    60 => 5300,
    64 => 5320,
    100 => 5500,
    104 => 5520,
    108 => 5540,
    112 => 5560,
    116 => 5580,
    120 => 5600,
    124 => 5620,
    128 => 5640,
    132 => 5660,
    136 => 5680,
    140 => 5700,
    149 => 5745,
    153 => 5765,
    157 => 5785,
    161 => 5805,
    165 => 5825
  }

  # Convert Wi-Fi channel number to frequency in MHz
  @spec channel_to_frequency(integer()) :: integer()
  defp channel_to_frequency(channel) when is_integer(channel) do
    Map.get(@channel_frequency_map, channel, 0)
  end

  defp channel_to_frequency(_), do: 0

  @impl true
  def wireless_oid_defs do
    [
      # Client count radio 1 (ng = 2.4GHz)
      %{
        oid: "1.3.6.1.4.1.41112.1.6.1.2.1.8.1",
        sensor_type: "clients",
        sensor_descr: "Clients (ng)",
        sensor_unit: "",
        sensor_divisor: 1
      },
      # Client count radio 2 (na = 5GHz)
      %{
        oid: "1.3.6.1.4.1.41112.1.6.1.2.1.8.2",
        sensor_type: "clients",
        sensor_descr: "Clients (na)",
        sensor_unit: "",
        sensor_divisor: 1
      },
      # CCQ (divide by 10 for %)
      %{
        oid: "1.3.6.1.4.1.41112.1.6.1.2.1.3.1",
        sensor_type: "ccq",
        sensor_descr: "CCQ Radio 1",
        sensor_unit: "%",
        sensor_divisor: 10
      },
      # CCQ Radio 2
      %{
        oid: "1.3.6.1.4.1.41112.1.6.1.2.1.3.2",
        sensor_type: "ccq",
        sensor_descr: "CCQ Radio 2",
        sensor_unit: "%",
        sensor_divisor: 10
      },
      # Tx Power Radio 1 in dBm
      %{
        oid: "1.3.6.1.4.1.41112.1.6.1.2.1.21.1",
        sensor_type: "power",
        sensor_descr: "Tx Power Radio 1",
        sensor_unit: "dBm",
        sensor_divisor: 1
      },
      # Tx Power Radio 2 in dBm
      %{
        oid: "1.3.6.1.4.1.41112.1.6.1.2.1.21.2",
        sensor_type: "power",
        sensor_descr: "Tx Power Radio 2",
        sensor_unit: "dBm",
        sensor_divisor: 1
      },
      # Total Channel Utilization Radio 1
      %{
        oid: "1.3.6.1.4.1.41112.1.6.1.1.1.6.1",
        sensor_type: "utilization",
        sensor_descr: "Total Utilization Radio 1",
        sensor_unit: "%",
        sensor_divisor: 1
      },
      # Total Channel Utilization Radio 2
      %{
        oid: "1.3.6.1.4.1.41112.1.6.1.1.1.6.2",
        sensor_type: "utilization",
        sensor_descr: "Total Utilization Radio 2",
        sensor_unit: "%",
        sensor_divisor: 1
      },
      # Self Rx Utilization Radio 1
      %{
        oid: "1.3.6.1.4.1.41112.1.6.1.1.1.7.1",
        sensor_type: "utilization",
        sensor_descr: "Self Rx Utilization Radio 1",
        sensor_unit: "%",
        sensor_divisor: 1
      },
      # Self Tx Utilization Radio 1
      %{
        oid: "1.3.6.1.4.1.41112.1.6.1.1.1.8.1",
        sensor_type: "utilization",
        sensor_descr: "Self Tx Utilization Radio 1",
        sensor_unit: "%",
        sensor_divisor: 1
      }
    ]
  end
end
