defmodule Towerops.Snmp.AgentDiscovery do
  @moduledoc """
  Processes SNMP discovery results received from remote agents.

  Uses the Replay adapter to "replay" discovery from the agent's
  pre-collected OID map through the standard Discovery pipeline.

  ## Workflow

  1. Receive SnmpResult with oid_values map from agent
  2. Create Replay adapter connection opts
  3. Run standard Discovery pipeline (reuses all profile logic)
  4. Save results to database

  This ensures agent discoveries are processed identically to
  direct Phoenix discoveries.

  ## Example

      iex> oid_values = %{
      ...>   "1.3.6.1.2.1.1.1.0" => "Cisco IOS",
      ...>   "1.3.6.1.2.1.2.2.1.1.1" => "1",
      ...>   "1.3.6.1.2.1.2.2.1.2.1" => "GigabitEthernet0/1"
      ...> }
      iex> AgentDiscovery.process_agent_discovery(device, oid_values)
      {:ok, %Device{}}
  """

  alias Towerops.Devices
  alias Towerops.Devices.Device, as: DeviceSchema
  alias Towerops.Snmp.Adapters.Replay
  alias Towerops.Snmp.Device, as: DiscoveredDevice
  alias Towerops.Snmp.Discovery

  require Logger

  @doc """
  Processes discovery results from an agent.

  Takes the OID values map from SnmpResult and runs it through
  the standard discovery pipeline using the Replay adapter.

  ## Parameters
  - device: The Device schema struct to discover
  - oid_values: Map of OID strings to value strings from agent

  ## Returns
  - `{:ok, discovered_device}` on success
  - `{:error, reason}` on failure

  ## Example

      iex> oid_values = %{
      ...>   "1.3.6.1.2.1.1.1.0" => "Cisco IOS Software",
      ...>   "1.3.6.1.2.1.2.2.1.1.1" => "1"
      ...> }
      iex> AgentDiscovery.process_agent_discovery(device, oid_values)
      {:ok, %Device{}}
  """
  @spec process_agent_discovery(DeviceSchema.t(), map()) ::
          {:ok, DiscoveredDevice.t()} | {:error, term()}
  def process_agent_discovery(device, oid_values) when is_map(oid_values) do
    oid_count = map_size(oid_values)

    Logger.debug("Processing agent discovery for #{device.name} (#{device.ip_address})",
      device_id: device.id,
      oid_count: oid_count
    )

    # Check for empty OID map - this indicates agent couldn't collect any SNMP data
    if oid_count == 0 do
      Logger.error(
        "Agent collected 0 OIDs for #{device.name} (#{device.ip_address})",
        device_id: device.id,
        snmp_port: device.snmp_port || 161,
        community: obscure_community(device)
      )

      {:error, :agent_collected_no_data}
    else
      # Build connection opts with Replay adapter
      client_opts = build_replay_opts(device, oid_values)

      # Run discovery using standard pipeline
      # This will call all the same functions as direct discovery,
      # but read from the OID map instead of making SNMP queries
      perform_agent_discovery(device, client_opts)
    end
  end

  defp perform_agent_discovery(device, client_opts) do
    case Discovery.discover_device_with_opts(device, client_opts) do
      {:ok, discovered_device} ->
        interfaces_count = count_interfaces(discovered_device)

        Logger.debug("Agent discovery succeeded for #{device.name}",
          device_id: device.id,
          interfaces: interfaces_count,
          sensors: count_sensors(discovered_device)
        )

        {:ok, discovered_device}

      {:error, reason} = error ->
        Logger.error("Agent discovery failed for #{device.name}: #{inspect(reason)}",
          device_id: device.id
        )

        error
    end
  end

  defp count_interfaces(discovered_device) do
    length(discovered_device.interfaces)
  end

  # Build connection opts for Replay adapter
  @spec build_replay_opts(DeviceSchema.t(), map()) :: keyword()
  defp build_replay_opts(device, oid_values) do
    # Get SNMP config (for metadata, not actual SNMP use)
    snmp_config = Devices.get_snmp_config(device)

    # Create Replay adapter opts
    # The Replay adapter will read from oid_values instead of making SNMP calls
    [
      ip: device.ip_address,
      community: snmp_config.community || "(from agent)",
      version: snmp_config.version,
      port: device.snmp_port || 161,
      adapter: Replay,
      oid_map: oid_values
    ]
  end

  # Count sensors in discovered device
  @spec count_sensors(DiscoveredDevice.t()) :: non_neg_integer()
  defp count_sensors(discovered_device) do
    case discovered_device.sensors do
      list when is_list(list) -> length(list)
      _ -> 0
    end
  end

  # Obscure community string for logging (show first 3 chars + ***)
  @spec obscure_community(DeviceSchema.t()) :: String.t()
  defp obscure_community(device) do
    snmp_config = Devices.get_snmp_config(device)
    community = snmp_config.community || "unknown"

    if String.length(community) > 3 do
      String.slice(community, 0..2) <> "***"
    else
      "***"
    end
  end
end
