defmodule Towerops.Snmp.Profiles.Cisco do
  @moduledoc """
  Cisco-specific SNMP device profile.
  Extends Base profile with Cisco-specific sensor discovery from CISCO-ENTITY-SENSOR-MIB.

  Discovers additional sensors:
  - Temperature sensors (chassis, modules, power supplies)
  - Power supply sensors
  - Fan speed sensors
  - Voltage sensors
  """

  alias Towerops.Snmp.Client
  alias Towerops.Snmp.Discovery
  alias Towerops.Snmp.Profiles.Base

  require Logger

  @cisco_sensor_oids %{
    # CISCO-ENTITY-SENSOR-MIB
    ent_sensor_type: "1.3.6.1.4.1.9.9.91.1.1.1.1.1",
    ent_sensor_scale: "1.3.6.1.4.1.9.9.91.1.1.1.1.2",
    ent_sensor_precision: "1.3.6.1.4.1.9.9.91.1.1.1.1.3",
    ent_sensor_value: "1.3.6.1.4.1.9.9.91.1.1.1.1.4",
    ent_sensor_status: "1.3.6.1.4.1.9.9.91.1.1.1.1.5",

    # ENTITY-MIB for physical descriptions
    ent_phys_descr: "1.3.6.1.2.1.47.1.1.1.1.2",
    ent_phys_name: "1.3.6.1.2.1.47.1.1.1.1.7"
  }

  @doc """
  Discovers system information using Base profile.
  """
  @spec discover_system_info(Client.connection_opts()) ::
          {:ok, Discovery.system_info()} | {:error, term()}
  def discover_system_info(client_opts) do
    Base.discover_system_info(client_opts)
  end

  @doc """
  Discovers network interfaces using Base profile.
  """
  @spec discover_interfaces(Client.connection_opts()) ::
          {:ok, [Discovery.interface_data()]} | {:error, term()}
  def discover_interfaces(client_opts) do
    Base.discover_interfaces(client_opts)
  end

  @doc """
  Discovers sensors from both standard ENTITY-SENSOR-MIB and CISCO-ENTITY-SENSOR-MIB.
  Cisco-specific sensors provide more detailed temperature, power, and fan monitoring.
  """
  @spec discover_sensors(Client.connection_opts()) ::
          {:ok, [Discovery.sensor_data()]} | {:error, term()}
  def discover_sensors(client_opts) do
    # Try Cisco-specific sensors first
    case discover_cisco_sensors(client_opts) do
      {:ok, [_ | _] = sensors} ->
        {:ok, sensors}

      _ ->
        # Fall back to standard ENTITY-SENSOR-MIB
        Logger.debug("Cisco-specific sensors not available, using standard discovery")
        Base.discover_sensors(client_opts)
    end
  end

  @doc """
  Identifies Cisco device model more accurately from sysDescr.
  """
  @spec identify_device(Discovery.system_info()) :: Discovery.device_info()
  def identify_device(system_info) do
    sys_descr = Map.get(system_info, :sys_descr, "")

    model = extract_cisco_model_detailed(sys_descr)

    Map.merge(system_info, %{
      manufacturer: "Cisco",
      model: model,
      firmware_version: extract_firmware_version(sys_descr)
    })
  end

  # Private functions

  defp discover_cisco_sensors(client_opts) do
    case Client.walk(client_opts, @cisco_sensor_oids.ent_sensor_type) do
      {:ok, sensor_types} when map_size(sensor_types) > 0 ->
        sensor_indices = extract_indices_from_oids(sensor_types)

        sensors =
          sensor_indices
          |> Enum.map(fn index ->
            build_cisco_sensor(client_opts, index)
          end)
          |> Enum.reject(&is_nil/1)

        {:ok, sensors}

      _ ->
        {:error, :cisco_sensors_not_available}
    end
  end

  defp build_cisco_sensor(client_opts, index) do
    oids = [
      @cisco_sensor_oids.ent_sensor_type <> ".#{index}",
      @cisco_sensor_oids.ent_sensor_scale <> ".#{index}",
      @cisco_sensor_oids.ent_sensor_precision <> ".#{index}",
      @cisco_sensor_oids.ent_sensor_value <> ".#{index}",
      @cisco_sensor_oids.ent_sensor_status <> ".#{index}",
      @cisco_sensor_oids.ent_phys_descr <> ".#{index}",
      @cisco_sensor_oids.ent_phys_name <> ".#{index}"
    ]

    case Client.get_multiple(client_opts, oids) do
      {:ok, [type, scale, precision, value, status, descr, name]} ->
        sensor_type = parse_cisco_sensor_type(type)
        divisor = calculate_divisor(scale, precision)

        %{
          sensor_type: sensor_type,
          sensor_index: "#{index}",
          sensor_oid: @cisco_sensor_oids.ent_sensor_value <> ".#{index}",
          sensor_descr: parse_sensor_description(descr, name),
          sensor_unit: cisco_sensor_type_to_unit(sensor_type),
          sensor_divisor: divisor,
          last_value: parse_sensor_value(value, divisor),
          status: parse_cisco_sensor_status(status)
        }

      {:error, reason} ->
        Logger.debug("Failed to fetch Cisco sensor #{index}: #{inspect(reason)}")
        nil
    end
  end

  defp extract_indices_from_oids(oid_map) do
    oid_map
    |> Map.keys()
    |> Enum.map(fn oid_string ->
      oid_string
      |> String.split(".")
      |> List.last()
      |> String.to_integer()
    end)
  end

  defp extract_cisco_model_detailed(sys_descr) do
    cond do
      # Catalyst switches: "Cisco IOS Software, C2960 Software..."
      match = Regex.run(~r/\b(C\d+\w*|WS-C\d+\w*)\b/, sys_descr) ->
        [_, model] = match
        model

      # ISR routers: "Cisco IOS Software, 1900 Software..."
      match = Regex.run(~r/\b(ISR\d+\w*|[0-9]{4})\b/, sys_descr) ->
        [_, model] = match
        model

      # ASR routers
      match = Regex.run(~r/\b(ASR\d+\w*)\b/, sys_descr) ->
        [_, model] = match
        model

      true ->
        "Unknown Cisco Model"
    end
  end

  defp extract_firmware_version(sys_descr) do
    # Try to extract version like "Version 15.2(4)E7"
    case Regex.run(~r/Version\s+([\d.()A-Za-z]+)/, sys_descr) do
      [_, version] -> version
      _ -> nil
    end
  end

  # CISCO-ENTITY-SENSOR-MIB sensor types
  defp parse_cisco_sensor_type(1), do: "other"
  defp parse_cisco_sensor_type(2), do: "unknown"
  defp parse_cisco_sensor_type(3), do: "volts_ac"
  defp parse_cisco_sensor_type(4), do: "volts_dc"
  defp parse_cisco_sensor_type(5), do: "amperes"
  defp parse_cisco_sensor_type(6), do: "watts"
  defp parse_cisco_sensor_type(7), do: "hertz"
  defp parse_cisco_sensor_type(8), do: "celsius"
  defp parse_cisco_sensor_type(9), do: "percent_rh"
  defp parse_cisco_sensor_type(10), do: "rpm"
  defp parse_cisco_sensor_type(11), do: "cmm"
  defp parse_cisco_sensor_type(12), do: "truthvalue"
  defp parse_cisco_sensor_type(13), do: "specialenum"
  defp parse_cisco_sensor_type(14), do: "dBm"
  defp parse_cisco_sensor_type(_), do: "unknown"

  defp cisco_sensor_type_to_unit("celsius"), do: "°C"
  defp cisco_sensor_type_to_unit("volts_ac"), do: "VAC"
  defp cisco_sensor_type_to_unit("volts_dc"), do: "VDC"
  defp cisco_sensor_type_to_unit("amperes"), do: "A"
  defp cisco_sensor_type_to_unit("watts"), do: "W"
  defp cisco_sensor_type_to_unit("hertz"), do: "Hz"
  defp cisco_sensor_type_to_unit("percent_rh"), do: "%RH"
  defp cisco_sensor_type_to_unit("rpm"), do: "RPM"
  defp cisco_sensor_type_to_unit("dBm"), do: "dBm"
  defp cisco_sensor_type_to_unit(_), do: ""

  # Calculate divisor from scale and precision
  # Scale: 10^scale (e.g., -3 = milli, 0 = units)
  # Precision: number of decimal places
  defp calculate_divisor(scale, _precision) when is_integer(scale) do
    case scale do
      -24 -> 1_000_000_000_000_000_000_000_000
      -12 -> 1_000_000_000_000
      -9 -> 1_000_000_000
      -6 -> 1_000_000
      -3 -> 1_000
      0 -> 1
      3 -> 0.001
      _ -> 1
    end
  end

  defp calculate_divisor(_, _), do: 1

  defp parse_sensor_value(value, divisor) when is_integer(value) and is_number(divisor) do
    value / divisor
  end

  defp parse_sensor_value(_, _), do: nil

  defp parse_sensor_description(descr, _name) when is_binary(descr) and byte_size(descr) > 0 do
    descr
  end

  defp parse_sensor_description(_descr, name) when is_binary(name) and byte_size(name) > 0 do
    name
  end

  defp parse_sensor_description(_, _), do: "Unknown Sensor"

  # CISCO-ENTITY-SENSOR-MIB sensor status
  defp parse_cisco_sensor_status(1), do: "ok"
  defp parse_cisco_sensor_status(2), do: "unavailable"
  defp parse_cisco_sensor_status(3), do: "nonoperational"
  defp parse_cisco_sensor_status(_), do: "unknown"
end
