defmodule Towerops.JobMonitoring.Metrics do
  @moduledoc """
  Calculates comprehensive health metrics for polling and discovery operations.
  """

  import Ecto.Query

  alias Oban.Job
  alias Towerops.Repo

  @worker_names [
    "Towerops.Workers.DevicePollerWorker",
    "Towerops.Workers.DiscoveryWorker"
  ]

  @type metrics :: %{
          executing_count: non_neg_integer(),
          queued_count: non_neg_integer(),
          completed_last_hour: non_neg_integer(),
          failed_last_hour: non_neg_integer(),
          polling_success_rate_1h: float(),
          discovery_success_rate_1h: float(),
          queue_depths: %{
            pollers: non_neg_integer(),
            discovery: non_neg_integer()
          },
          avg_execution_time_seconds: float() | nil
        }

  @doc """
  Calculates all health metrics for job monitoring.

  Returns a map containing:
  - `executing_count` - Number of currently executing jobs
  - `queued_count` - Number of queued jobs (scheduled/available)
  - `completed_last_hour` - Number of completed jobs in the last hour
  - `failed_last_hour` - Number of failed jobs in the last hour
  - `polling_success_rate_1h` - Success rate for polling jobs (0.0-1.0)
  - `discovery_success_rate_1h` - Success rate for discovery jobs (0.0-1.0)
  - `queue_depths` - Queue depths by queue name
  - `avg_execution_time_seconds` - Average execution time in seconds
  """
  @spec calculate_all() :: metrics()
  def calculate_all do
    now = DateTime.utc_now()
    one_hour_ago = DateTime.add(now, -3600, :second)

    %{
      executing_count: count_executing(),
      queued_count: count_queued(),
      completed_last_hour: count_completed_since(one_hour_ago),
      failed_last_hour: count_failed_since(one_hour_ago),
      polling_success_rate_1h: calculate_success_rate("Towerops.Workers.DevicePollerWorker", one_hour_ago),
      discovery_success_rate_1h: calculate_success_rate("Towerops.Workers.DiscoveryWorker", one_hour_ago),
      queue_depths: calculate_queue_depths(),
      avg_execution_time_seconds: calculate_avg_execution_time(one_hour_ago)
    }
  end

  defp count_executing do
    Repo.one(from(j in Job, where: j.state == "executing", where: j.worker in ^@worker_names, select: count(j.id)))
  end

  defp count_queued do
    Repo.one(
      from(j in Job, where: j.state in ["available", "scheduled"], where: j.worker in ^@worker_names, select: count(j.id))
    )
  end

  defp count_completed_since(since) do
    Repo.one(
      from(j in Job,
        where: j.state == "completed",
        where: j.worker in ^@worker_names,
        where: j.completed_at >= ^since,
        select: count(j.id)
      )
    )
  end

  defp count_failed_since(since) do
    Repo.one(
      from(j in Job,
        where: j.state in ["retryable", "cancelled", "discarded"],
        where: j.worker in ^@worker_names,
        where: j.inserted_at >= ^since,
        select: count(j.id)
      )
    )
  end

  defp calculate_success_rate(worker, since) do
    completed =
      Repo.one(
        from(j in Job,
          where: j.worker == ^worker,
          where: j.state == "completed",
          where: j.completed_at >= ^since,
          select: count(j.id)
        )
      )

    failed =
      Repo.one(
        from(j in Job,
          where: j.worker == ^worker,
          where: j.state in ["retryable", "cancelled", "discarded"],
          where: j.inserted_at >= ^since,
          select: count(j.id)
        )
      )

    total = completed + failed

    if total == 0 do
      0.0
    else
      completed / total
    end
  end

  defp calculate_queue_depths do
    depths =
      from(j in Job,
        where: j.state in ["available", "scheduled"],
        where: j.worker in ^@worker_names,
        group_by: j.queue,
        select: {j.queue, count(j.id)}
      )
      |> Repo.all()
      |> Map.new()

    %{
      pollers: Map.get(depths, "pollers", 0),
      discovery: Map.get(depths, "discovery", 0)
    }
  end

  defp calculate_avg_execution_time(since) do
    result =
      Repo.one(
        from(j in Job,
          where: j.state == "completed",
          where: j.worker in ^@worker_names,
          where: j.completed_at >= ^since,
          where: not is_nil(j.attempted_at),
          where: not is_nil(j.completed_at),
          select: avg(fragment("EXTRACT(EPOCH FROM (? - ?))", j.completed_at, j.attempted_at))
        )
      )

    case result do
      nil -> nil
      %Decimal{} = decimal -> Decimal.to_float(decimal)
      value when is_float(value) -> value
    end
  end
end
