defmodule Towerops.Monitoring.Supervisor do
  @moduledoc """
  Supervisor for managing device monitoring workers.
  """
  use Supervisor

  alias Towerops.Monitoring.DeviceMonitor
  alias Towerops.Snmp.NeighborCleanupWorker
  alias Towerops.Snmp.PollerRegistry
  alias Towerops.Snmp.PollerWorker

  # Note: DynamicSupervisor names are process names for Horde, not module aliases

  require Logger

  def start_link(init_arg) do
    Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
  end

  @impl true
  def init(_init_arg) do
    base_children = [
      # Horde.Registry for distributed process naming (cluster-aware)
      {Horde.Registry, keys: :unique, name: Towerops.Monitoring.Registry, members: :auto},
      # Horde.Registry for SNMP poller processes (cluster-aware)
      {Horde.Registry, keys: :unique, name: PollerRegistry, members: :auto},
      # Task.Supervisor for parallel polling operations (local to each node)
      {Task.Supervisor, name: Towerops.Snmp.PollerTaskSupervisor},
      # Horde.DynamicSupervisor for monitor workers (distributed across cluster)
      {Horde.DynamicSupervisor, name: DynamicSupervisor, strategy: :one_for_one, members: :auto},
      # Horde.DynamicSupervisor for SNMP poller workers (distributed across cluster)
      {Horde.DynamicSupervisor, name: PollerSupervisor, strategy: :one_for_one, members: :auto}
    ]

    # Only start cleanup worker in non-test environments
    children =
      if test_mode?() do
        base_children
      else
        base_children ++ [NeighborCleanupWorker]
      end

    # Start all monitors after supervisor is initialized (but not in test mode)
    # In test mode, the Repo uses Sandbox pool which we can detect
    _ =
      if !test_mode?() do
        Task.start(fn ->
          # Wait briefly for the supervisor tree to be fully initialized
          Process.sleep(100)

          try do
            start_all_monitors()
            Logger.info("Started all devices monitors")
          rescue
            error ->
              Logger.error("Failed to start monitors: #{inspect(error)}")
          end

          try do
            start_all_snmp_pollers()
            Logger.info("Started all SNMP pollers")
          rescue
            error ->
              Logger.error("Failed to start SNMP pollers: #{inspect(error)}")
          end
        end)
      end

    Supervisor.init(children, strategy: :one_for_one)
  end

  @doc """
  Starts monitoring for a specific device.
  Cluster-aware: only one monitor per device across all nodes.
  """
  def start_monitor(device_id) do
    spec = {DeviceMonitor, device_id}

    Horde.DynamicSupervisor.start_child(DynamicSupervisor, spec)
  end

  @doc """
  Stops monitoring for a specific device.
  Cluster-aware: stops the monitor regardless of which node it's running on.
  """
  def stop_monitor(device_id) do
    case Horde.Registry.lookup(Towerops.Monitoring.Registry, device_id) do
      [{pid, _}] ->
        Horde.DynamicSupervisor.terminate_child(DynamicSupervisor, pid)

      [] ->
        :ok
    end
  end

  @doc """
  Starts monitors for all devices that has monitoring enabled.
  """
  def start_all_monitors do
    Enum.each(Towerops.Devices.list_monitored_devices(), fn device ->
      start_monitor(device.id)
    end)
  end

  @doc """
  Starts an SNMP poller for a specific device.
  Cluster-aware: only one poller per device across all nodes.
  """
  def start_snmp_poller(device_id) do
    spec = {PollerWorker, device_id}
    Horde.DynamicSupervisor.start_child(PollerSupervisor, spec)
  end

  @doc """
  Stops an SNMP poller for a specific device.
  Cluster-aware: stops the poller regardless of which node it's running on.
  """
  def stop_snmp_poller(device_id) do
    case Horde.Registry.lookup(PollerRegistry, device_id) do
      [{pid, _}] ->
        Horde.DynamicSupervisor.terminate_child(PollerSupervisor, pid)

      [] ->
        :ok
    end
  end

  @doc """
  Starts SNMP pollers for all devices that has SNMP enabled.
  """
  def start_all_snmp_pollers do
    Enum.each(Towerops.Devices.list_snmp_enabled_devices(), fn device ->
      start_snmp_poller(device.id)
    end)
  end

  # Check if running in test mode by inspecting the Repo pool configuration
  defp test_mode? do
    config = Application.get_env(:towerops, Towerops.Repo, [])
    Keyword.get(config, :pool) == Ecto.Adapters.SQL.Sandbox
  end
end
