defmodule Mix.Tasks.Oban.CancelStuckDiscovery do
  @shortdoc "Cancels stuck DiscoveryWorker jobs"

  @moduledoc """
  Cancels all stuck DiscoveryWorker jobs that are in scheduled or retryable state.

  ## Usage

      # In production via kubectl:
      kubectl exec -n towerops deployment/towerops -- /app/bin/towerops rpc "Mix.Tasks.Oban.CancelStuckDiscovery.run([])"

      # Locally:
      mix oban.cancel_stuck_discovery
  """
  use Mix.Task

  import Ecto.Query

  require Logger

  @impl Mix.Task
  def run(_args) do
    Mix.Task.run("app.start")

    cancelled_count =
      Oban.Job
      |> where([j], j.worker == "Towerops.Workers.DiscoveryWorker")
      |> where([j], j.state in ["scheduled", "retryable", "executing"])
      |> Towerops.Repo.all()
      |> Enum.map(fn job ->
        case Oban.cancel_job(Oban, job.id) do
          {:ok, cancelled_job} ->
            Logger.info(
              "Cancelled stuck discovery job",
              job_id: cancelled_job.id,
              device_id: get_in(cancelled_job.args, ["device_id"]),
              state: cancelled_job.state,
              attempted_at: cancelled_job.attempted_at
            )

            1

          :ok ->
            # Some versions of Oban return :ok instead of {:ok, job}
            Logger.info(
              "Cancelled stuck discovery job",
              job_id: job.id,
              device_id: get_in(job.args, ["device_id"]),
              state: job.state,
              attempted_at: job.attempted_at
            )

            1

          {:error, reason} ->
            Logger.error(
              "Failed to cancel job",
              job_id: job.id,
              error: inspect(reason)
            )

            0
        end
      end)
      |> Enum.sum()

    Mix.shell().info("Cancelled #{cancelled_count} stuck discovery jobs")
  end
end
