defmodule Towerops.Workers.JobHealthCheckWorker do
  @moduledoc """
  Oban worker that ensures all enabled devices have active monitoring jobs.

  Runs every 10 minutes via Oban.Plugins.Cron as a safety net to:
  1. Find devices with monitoring_enabled=true but no active DeviceMonitorWorker job
  2. Automatically create missing jobs
  3. Log when missing jobs are found and recovered

  Note: SNMP polling is handled by per-check CheckExecutorWorker jobs, not device-level jobs.

  This handles edge cases where jobs might get cancelled unexpectedly or database
  inconsistencies occur during deployments or pod failures.
  """
  use Oban.Worker, queue: :maintenance

  import Ecto.Query

  alias Towerops.Devices
  alias Towerops.Repo
  alias Towerops.Workers.DeviceMonitorWorker

  require Logger

  @impl Oban.Worker
  @spec perform(Oban.Job.t()) :: :ok
  def perform(%Oban.Job{}) do
    monitor_recoveries = recover_missing_monitor_jobs()

    if monitor_recoveries > 0 do
      Logger.warning("Job health check recovered #{monitor_recoveries} missing monitor job(s)")
    else
      Logger.debug("Job health check completed: all jobs healthy")
    end

    :ok
  end

  defp recover_missing_monitor_jobs do
    devices_with_monitoring = Devices.list_monitored_devices()
    active_monitor_job_device_ids = get_active_monitor_job_device_ids()

    missing_monitor_devices = Enum.reject(devices_with_monitoring, &MapSet.member?(active_monitor_job_device_ids, &1.id))

    Enum.each(missing_monitor_devices, fn device ->
      Logger.debug(
        "Device '#{device.name}' has monitoring_enabled=true but no active monitor job, creating job",
        device_id: device.id,
        device_name: device.name
      )

      DeviceMonitorWorker.start_monitoring(device.id)
    end)

    length(missing_monitor_devices)
  end

  defp get_active_monitor_job_device_ids do
    Oban.Job
    |> where([j], j.worker == "Towerops.Workers.DeviceMonitorWorker")
    |> where([j], j.state in ["available", "scheduled", "executing", "retryable"])
    |> select([j], fragment("args->>'device_id'"))
    |> Repo.all()
    |> MapSet.new()
  end
end
