defmodule Towerops.Snmp.Profiles.Vendors.Mikrotik do
  @moduledoc """
  SNMP profile for Mikrotik devices.

  Auto-generated from YAML profiles. Add vendor-specific OIDs as needed.
  """

  @behaviour Towerops.Snmp.Profiles.Vendors.Vendor

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

  # Enterprise OID: 1.3.6.1.4.1.14988

  @impl true
  # Note: "routeros" is handled by the Routeros module
  def profile_names, do: ["swos"]

  @impl true
  def detect_hardware(client_opts) do
    case Client.get(client_opts, "1.3.6.1.2.1.1.1.0") do
      {:ok, descr} when is_binary(descr) ->
        parse_hardware_from_descr(descr)

      _ ->
        nil
    end
  end

  defp parse_hardware_from_descr(descr) do
    # For now, return first line or word from sysDescr
    descr
    |> String.split(~r/[\r\n]/)
    |> List.first()
    |> case do
      nil -> nil
      line -> String.slice(line, 0, 64)
    end
  end

  @impl true
  def wireless_oid_defs do
    [
      # Standard HOST-RESOURCES-MIB CPU
      %{
        oid: "1.3.6.1.2.1.25.3.3.1.2.1",
        sensor_descr: "CPU Load",
        sensor_type: "load",
        sensor_unit: "%",
        sensor_divisor: 1
      }
    ]
  end

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

    # Discover dynamic wireless sensors from MikroTik MIB tables
    wireless_sensors =
      discover_ap_sensors(client_opts) ++
        discover_stat_sensors(client_opts) ++
        discover_60g_sensors(client_opts) ++
        discover_60g_sta_sensors(client_opts) ++
        discover_lte_sensors(client_opts)

    static_sensors ++ wireless_sensors
  end

  # Discover sensors from mtxrWlApTable (AP interface stats)
  @spec discover_ap_sensors(Client.connection_opts()) :: [map()]
  defp discover_ap_sensors(client_opts) do
    case Client.walk(client_opts, "1.3.6.1.4.1.14988.1.1.1.3") do
      {:ok, data} when map_size(data) > 0 ->
        build_ap_sensors(data)

      _ ->
        []
    end
  end

  @spec build_ap_sensors(map()) :: [map()]
  defp build_ap_sensors(data) do
    # Group data by interface index
    grouped = group_by_index(data)

    Enum.flat_map(grouped, fn {index, fields} ->
      ssid = Map.get(fields, "1.3.6.1.4.1.14988.1.1.1.3.1.4.#{index}", "Unknown")
      freq = Map.get(fields, "1.3.6.1.4.1.14988.1.1.1.3.1.7.#{index}", "0")
      freq_label = freq_to_label(freq)

      client_count = Map.get(fields, "1.3.6.1.4.1.14988.1.1.1.3.1.6.#{index}", 0)
      ccq = Map.get(fields, "1.3.6.1.4.1.14988.1.1.1.3.1.10.#{index}", 0)
      tx_rate = Map.get(fields, "1.3.6.1.4.1.14988.1.1.1.3.1.2.#{index}", 0)
      rx_rate = Map.get(fields, "1.3.6.1.4.1.14988.1.1.1.3.1.3.#{index}", 0)
      noise_floor = Map.get(fields, "1.3.6.1.4.1.14988.1.1.1.3.1.9.#{index}")

      sensors = []

      # Clients
      sensors =
        sensors ++
          [
            %{
              oid: "1.3.6.1.4.1.14988.1.1.1.3.1.6.#{index}",
              sensor_type: "clients",
              sensor_descr: "#{freq_label}: #{ssid}",
              sensor_unit: "",
              sensor_divisor: 1,
              sensor_index: "mikrotik_clients_#{index}",
              last_value: client_count
            }
          ]

      # CCQ (skip if clients exist but CCQ is 0 - nv2 reports 1 client but no CCQ)
      sensors =
        if client_count > 0 && ccq == 0 do
          sensors
        else
          sensors ++
            [
              %{
                oid: "1.3.6.1.4.1.14988.1.1.1.3.1.10.#{index}",
                sensor_type: "ccq",
                sensor_descr: "#{freq_label}: #{ssid}",
                sensor_unit: "%",
                sensor_divisor: 1,
                sensor_index: "mikrotik_ccq_#{index}",
                last_value: ccq
              }
            ]
        end

      # Frequency (skip if 0)
      sensors =
        if freq == "0" || freq == 0 do
          sensors
        else
          sensors ++
            [
              %{
                oid: "1.3.6.1.4.1.14988.1.1.1.3.1.7.#{index}",
                sensor_type: "frequency",
                sensor_descr: "#{freq_label}: #{ssid}",
                sensor_unit: "MHz",
                sensor_divisor: 1,
                sensor_index: "mikrotik_freq_#{index}",
                last_value: to_integer(freq)
              }
            ]
        end

      # Noise Floor
      sensors =
        if noise_floor do
          sensors ++
            [
              %{
                oid: "1.3.6.1.4.1.14988.1.1.1.3.1.9.#{index}",
                sensor_type: "noise-floor",
                sensor_descr: "#{freq_label}: #{ssid}",
                sensor_unit: "dBm",
                sensor_divisor: 1,
                sensor_index: "mikrotik_noise_#{index}",
                last_value: noise_floor
              }
            ]
        else
          sensors
        end

      # TX/RX Rate (skip if both 0)
      if tx_rate != 0 || rx_rate != 0 do
        sensors ++
          [
            %{
              oid: "1.3.6.1.4.1.14988.1.1.1.3.1.2.#{index}",
              sensor_type: "rate",
              sensor_descr: "#{freq_label}: #{ssid} Tx",
              sensor_unit: "bps",
              sensor_divisor: 1,
              sensor_index: "mikrotik_tx_rate_#{index}",
              last_value: tx_rate
            },
            %{
              oid: "1.3.6.1.4.1.14988.1.1.1.3.1.3.#{index}",
              sensor_type: "rate",
              sensor_descr: "#{freq_label}: #{ssid} Rx",
              sensor_unit: "bps",
              sensor_divisor: 1,
              sensor_index: "mikrotik_rx_rate_#{index}",
              last_value: rx_rate
            }
          ]
      else
        sensors
      end
    end)
  end

  # Discover sensors from mtxrWlStatTable (Station stats)
  @spec discover_stat_sensors(Client.connection_opts()) :: [map()]
  defp discover_stat_sensors(client_opts) do
    case Client.walk(client_opts, "1.3.6.1.4.1.14988.1.1.1.1") do
      {:ok, data} when map_size(data) > 0 ->
        build_stat_sensors(data)

      _ ->
        []
    end
  end

  @spec build_stat_sensors(map()) :: [map()]
  defp build_stat_sensors(data) do
    grouped = group_by_index(data)

    Enum.flat_map(grouped, fn {index, fields} ->
      ssid = Map.get(fields, "1.3.6.1.4.1.14988.1.1.1.1.1.5.#{index}", "Unknown")
      freq = Map.get(fields, "1.3.6.1.4.1.14988.1.1.1.1.1.7.#{index}", "0")
      freq_label = freq_to_label(freq)

      tx_ccq = Map.get(fields, "1.3.6.1.4.1.14988.1.1.1.1.1.9.#{index}", 0)
      rx_ccq = Map.get(fields, "1.3.6.1.4.1.14988.1.1.1.1.1.10.#{index}", 0)
      tx_rate = Map.get(fields, "1.3.6.1.4.1.14988.1.1.1.1.1.2.#{index}", 0)
      rx_rate = Map.get(fields, "1.3.6.1.4.1.14988.1.1.1.1.1.3.#{index}", 0)

      sensors = []

      # TX/RX CCQ (skip if both empty)
      sensors =
        if tx_ccq > 0 || rx_ccq > 0 do
          sensors ++
            [
              %{
                oid: "1.3.6.1.4.1.14988.1.1.1.1.1.9.#{index}",
                sensor_type: "ccq",
                sensor_descr: "#{freq_label}: #{ssid} Tx",
                sensor_unit: "%",
                sensor_divisor: 1,
                sensor_index: "mikrotik_tx_ccq_#{index}",
                last_value: tx_ccq
              },
              %{
                oid: "1.3.6.1.4.1.14988.1.1.1.1.1.10.#{index}",
                sensor_type: "ccq",
                sensor_descr: "#{freq_label}: #{ssid} Rx",
                sensor_unit: "%",
                sensor_divisor: 1,
                sensor_index: "mikrotik_rx_ccq_#{index}",
                last_value: rx_ccq
              }
            ]
        else
          sensors
        end

      # Frequency (skip if 0)
      sensors =
        if freq == "0" || freq == 0 do
          sensors
        else
          sensors ++
            [
              %{
                oid: "1.3.6.1.4.1.14988.1.1.1.1.1.7.#{index}",
                sensor_type: "frequency",
                sensor_descr: "#{freq_label}: #{ssid}",
                sensor_unit: "MHz",
                sensor_divisor: 1,
                sensor_index: "mikrotik_stat_freq_#{index}",
                last_value: to_integer(freq)
              }
            ]
        end

      # TX/RX Rate (skip if both 0)
      if tx_rate != 0 || rx_rate != 0 do
        sensors ++
          [
            %{
              oid: "1.3.6.1.4.1.14988.1.1.1.1.1.2.#{index}",
              sensor_type: "rate",
              sensor_descr: "#{freq_label}: #{ssid} Tx",
              sensor_unit: "bps",
              sensor_divisor: 1,
              sensor_index: "mikrotik_stat_tx_rate_#{index}",
              last_value: tx_rate
            },
            %{
              oid: "1.3.6.1.4.1.14988.1.1.1.1.1.3.#{index}",
              sensor_type: "rate",
              sensor_descr: "#{freq_label}: #{ssid} Rx",
              sensor_unit: "bps",
              sensor_divisor: 1,
              sensor_index: "mikrotik_stat_rx_rate_#{index}",
              last_value: rx_rate
            }
          ]
      else
        sensors
      end
    end)
  end

  # Discover sensors from mtxrWl60GTable (60GHz wireless)
  @spec discover_60g_sensors(Client.connection_opts()) :: [map()]
  defp discover_60g_sensors(client_opts) do
    case Client.walk(client_opts, "1.3.6.1.4.1.14988.1.1.1.8") do
      {:ok, data} when map_size(data) > 0 ->
        build_60g_sensors(data)

      _ ->
        []
    end
  end

  @spec build_60g_sensors(map()) :: [map()]
  defp build_60g_sensors(data) do
    grouped = group_by_index(data)

    Enum.flat_map(grouped, fn {index, fields} ->
      ssid = Map.get(fields, "1.3.6.1.4.1.14988.1.1.1.8.1.7.#{index}", "Unknown")
      freq = Map.get(fields, "1.3.6.1.4.1.14988.1.1.1.8.1.6.#{index}", 0)
      rssi = Map.get(fields, "1.3.6.1.4.1.14988.1.1.1.8.1.12.#{index}", 0)
      quality = Map.get(fields, "1.3.6.1.4.1.14988.1.1.1.8.1.8.#{index}", 0)
      phy_rate = Map.get(fields, "1.3.6.1.4.1.14988.1.1.1.8.1.13.#{index}", 0)

      [
        %{
          oid: "1.3.6.1.4.1.14988.1.1.1.8.1.6.#{index}",
          sensor_type: "frequency",
          sensor_descr: "60G: #{ssid}",
          sensor_unit: "MHz",
          sensor_divisor: 1,
          sensor_index: "mikrotik_60g_freq_#{index}",
          last_value: freq
        },
        %{
          oid: "1.3.6.1.4.1.14988.1.1.1.8.1.12.#{index}",
          sensor_type: "rssi",
          sensor_descr: "60G: #{ssid}",
          sensor_unit: "dBm",
          sensor_divisor: 1,
          sensor_index: "mikrotik_60g_rssi_#{index}",
          last_value: rssi
        },
        %{
          oid: "1.3.6.1.4.1.14988.1.1.1.8.1.8.#{index}",
          sensor_type: "quality",
          sensor_descr: "60G: #{ssid}",
          sensor_unit: "%",
          sensor_divisor: 1,
          sensor_index: "mikrotik_60g_quality_#{index}",
          last_value: quality
        },
        %{
          oid: "1.3.6.1.4.1.14988.1.1.1.8.1.13.#{index}",
          sensor_type: "rate",
          sensor_descr: "60G: #{ssid}",
          sensor_unit: "bps",
          sensor_divisor: 1_000_000,
          sensor_index: "mikrotik_60g_rate_#{index}",
          last_value: phy_rate
        }
      ]
    end)
  end

  # Discover sensors from mtxrWl60GStaTable (60GHz station stats)
  @spec discover_60g_sta_sensors(Client.connection_opts()) :: [map()]
  defp discover_60g_sta_sensors(client_opts) do
    case Client.walk(client_opts, "1.3.6.1.4.1.14988.1.1.1.9") do
      {:ok, data} when map_size(data) > 0 ->
        build_60g_sta_sensors(data)

      _ ->
        []
    end
  end

  @spec build_60g_sta_sensors(map()) :: [map()]
  defp build_60g_sta_sensors(data) do
    grouped = group_by_index(data)

    Enum.flat_map(grouped, fn {index, fields} ->
      remote = Map.get(fields, "1.3.6.1.4.1.14988.1.1.1.9.1.4.#{index}", "Unknown")
      distance = Map.get(fields, "1.3.6.1.4.1.14988.1.1.1.9.1.10.#{index}", 0)

      [
        %{
          oid: "1.3.6.1.4.1.14988.1.1.1.9.1.10.#{index}",
          sensor_type: "distance",
          sensor_descr: "60G: Sta > #{remote}",
          sensor_unit: "km",
          sensor_divisor: 100_000,
          sensor_index: "mikrotik_60g_distance_#{index}",
          last_value: distance
        }
      ]
    end)
  end

  # Discover sensors from mtxrLTEModemTable (LTE modem stats)
  @spec discover_lte_sensors(Client.connection_opts()) :: [map()]
  defp discover_lte_sensors(client_opts) do
    case Client.walk(client_opts, "1.3.6.1.4.1.14988.1.1.16.1") do
      {:ok, data} when map_size(data) > 0 ->
        build_lte_sensors(data)

      _ ->
        []
    end
  end

  @spec build_lte_sensors(map()) :: [map()]
  defp build_lte_sensors(data) do
    grouped = group_by_index(data)

    Enum.flat_map(grouped, fn {index, fields} ->
      rsrq = Map.get(fields, "1.3.6.1.4.1.14988.1.1.16.1.1.3.#{index}", 0)
      rsrp = Map.get(fields, "1.3.6.1.4.1.14988.1.1.16.1.1.4.#{index}", 0)
      sinr = Map.get(fields, "1.3.6.1.4.1.14988.1.1.16.1.1.7.#{index}", 0)

      # We would need to walk mtxrInterfaceStatsName to get interface names
      # For now, use "LTE" prefix
      [
        %{
          oid: "1.3.6.1.4.1.14988.1.1.16.1.1.3.#{index}",
          sensor_type: "rsrq",
          sensor_descr: "LTE: Signal RSRQ",
          sensor_unit: "dB",
          sensor_divisor: 1,
          sensor_index: "mikrotik_lte_rsrq_#{index}",
          last_value: rsrq
        },
        %{
          oid: "1.3.6.1.4.1.14988.1.1.16.1.1.4.#{index}",
          sensor_type: "rsrp",
          sensor_descr: "LTE: Signal RSRP",
          sensor_unit: "dBm",
          sensor_divisor: 1,
          sensor_index: "mikrotik_lte_rsrp_#{index}",
          last_value: rsrp
        },
        %{
          oid: "1.3.6.1.4.1.14988.1.1.16.1.1.7.#{index}",
          sensor_type: "sinr",
          sensor_descr: "LTE: Signal SINR",
          sensor_unit: "dB",
          sensor_divisor: 1,
          sensor_index: "mikrotik_lte_sinr_#{index}",
          last_value: sinr
        }
      ]
    end)
  end

  # Extract frequency label from frequency value (2437 → "2G", 5180 → "5G")
  @spec freq_to_label(String.t() | integer()) :: String.t()
  defp freq_to_label(freq) when is_binary(freq) do
    case String.first(freq) do
      nil -> "SSID"
      char -> "#{char}G"
    end
  end

  defp freq_to_label(freq) when is_integer(freq) do
    freq_to_label(Integer.to_string(freq))
  end

  # Safely convert to integer (handles both string and integer inputs)
  @spec to_integer(String.t() | integer()) :: integer()
  defp to_integer(value) when is_integer(value), do: value
  defp to_integer(value) when is_binary(value), do: String.to_integer(value)

  # Group SNMP walk results by interface index
  @spec group_by_index(map()) :: map()
  defp group_by_index(data) do
    Enum.reduce(data, %{}, fn {oid, value}, acc ->
      # Extract index from OID (last number after last dot)
      index = oid |> String.split(".") |> List.last()

      # Store field value under this index
      Map.update(acc, index, %{oid => value}, fn fields ->
        Map.put(fields, oid, value)
      end)
    end)
  end
end
