defmodule Towerops.Snmp.Profiles.Vendors.Vendor do
  @moduledoc """
  Behaviour for vendor-specific SNMP device handling.

  Each vendor module (Epmp, Airos, Routeros, etc.) implements this behaviour
  to provide device-specific discovery logic, mirroring LibreNMS OS PHP classes.
  """

  alias Towerops.Snmp.Client

  @type sensor :: %{
          sensor_type: String.t(),
          sensor_index: String.t(),
          sensor_oid: String.t(),
          sensor_descr: String.t(),
          sensor_unit: String.t(),
          sensor_divisor: integer(),
          last_value: float() | nil
        }

  @type sensor_def :: %{
          oid: String.t(),
          sensor_type: String.t(),
          sensor_descr: String.t(),
          sensor_unit: String.t(),
          sensor_divisor: integer()
        }

  @doc """
  Returns the list of profile names this vendor module handles.
  """
  @callback profile_names() :: [String.t()]

  @doc """
  Detect hardware type/model from SNMP data.
  Returns a string describing the hardware (e.g., "ePMP AP", "MikroTik hAP ac2").
  """
  @callback detect_hardware(Client.connection_opts()) :: String.t() | nil

  @doc """
  Discover wireless sensors specific to this vendor.
  Returns a list of sensor maps ready to be saved to the database.
  """
  @callback discover_wireless_sensors(Client.connection_opts()) :: [sensor()]

  @doc """
  Returns the list of wireless OID definitions for debug data collection.
  """
  @callback wireless_oid_defs() :: [sensor_def()]

  # Helper functions shared by all vendor modules

  @doc """
  Fetch wireless sensor values from scalar OIDs.
  """
  @spec fetch_sensors([sensor_def()], Client.connection_opts()) :: [sensor()]
  def fetch_sensors(sensor_defs, client_opts) do
    sensor_defs
    |> Enum.map(&fetch_sensor_value(&1, client_opts))
    |> Enum.reject(&is_nil/1)
  end

  @doc """
  Fetch a single sensor value from a scalar OID.
  """
  @spec fetch_sensor_value(sensor_def(), Client.connection_opts()) :: sensor() | nil
  def fetch_sensor_value(sensor_def, client_opts) do
    case Client.get(client_opts, sensor_def.oid) do
      {:ok, value} when is_number(value) ->
        # Use OID suffix to make sensor_index unique
        oid_suffix = sensor_def.oid |> String.split(".") |> Enum.take(-2) |> Enum.join("_")
        descr_slug = sensor_def.sensor_descr |> String.downcase() |> String.replace(~r/[^a-z0-9]+/, "_")

        %{
          sensor_type: sensor_def.sensor_type,
          sensor_index: "wireless_#{descr_slug}_#{oid_suffix}",
          sensor_oid: sensor_def.oid,
          sensor_descr: sensor_def.sensor_descr,
          sensor_unit: sensor_def.sensor_unit,
          sensor_divisor: sensor_def.sensor_divisor,
          last_value: value / 1
        }

      _ ->
        nil
    end
  end

  @doc """
  Walk an SNMP table and create sensors from the results.
  Used for per-interface or per-client wireless sensors.
  """
  @spec walk_table_sensors(String.t(), sensor_def(), Client.connection_opts()) :: [sensor()]
  def walk_table_sensors(base_oid, sensor_def, client_opts) do
    case Client.walk(client_opts, base_oid) do
      {:ok, results} when is_map(results) and map_size(results) > 0 ->
        results
        |> Enum.with_index(1)
        |> Enum.map(fn {{oid, value}, idx} ->
          build_table_sensor(sensor_def, oid, value, idx)
        end)
        |> Enum.reject(&is_nil/1)

      _ ->
        []
    end
  end

  defp build_table_sensor(sensor_def, oid, value, idx) when is_number(value) do
    %{
      sensor_type: sensor_def.sensor_type,
      sensor_index: "#{sensor_def.sensor_type}_#{idx}",
      sensor_oid: oid,
      sensor_descr: "#{sensor_def.sensor_descr} #{idx}",
      sensor_unit: sensor_def.sensor_unit,
      sensor_divisor: sensor_def.sensor_divisor,
      last_value: value / 1
    }
  end

  defp build_table_sensor(_sensor_def, _oid, _value, _idx), do: nil
end
