defmodule Towerops.RedisHealthCheck do
  @moduledoc """
  Health check for Redis/Valkey connectivity.

  Performs a simple PING command to verify Redis is reachable and responding.
  Used by the health check endpoint to determine overall application health.
  """

  require Logger

  @doc """
  Waits for Redis to become available with exponential backoff retries.

  ## Parameters
  - `redis_config` - Keyword list with Redis connection config (host, port, password)
  - `opts` - Options keyword list:
    - `:max_attempts` - Maximum number of connection attempts (default: 10)
    - `:backoff` - Initial backoff time in milliseconds (default: 500)
    - `:timeout` - Connection timeout in milliseconds (default: 2000)

  ## Returns
  - `:ok` if Redis becomes available
  - `{:error, :max_attempts_exceeded}` if all attempts fail
  """
  @spec wait_for_redis(keyword(), keyword()) :: :ok | {:error, :max_attempts_exceeded}
  def wait_for_redis(redis_config, opts \\ []) do
    max_attempts = Keyword.get(opts, :max_attempts, 10)
    backoff = Keyword.get(opts, :backoff, 500)
    timeout = Keyword.get(opts, :timeout, 2_000)

    do_wait_for_redis(redis_config, timeout, max_attempts, backoff, 1)
  end

  defp do_wait_for_redis(_redis_config, _timeout, max_attempts, _backoff, attempt) when attempt > max_attempts do
    Logger.warning("Redis connection failed after #{max_attempts} attempts")
    {:error, :max_attempts_exceeded}
  end

  defp do_wait_for_redis(redis_config, timeout, max_attempts, backoff, attempt) do
    case check_health(redis_config, timeout) do
      :ok ->
        if attempt > 1 do
          Logger.info("Redis connection established after #{attempt} attempts")
        end

        :ok

      {:error, reason} ->
        wait_time = round(backoff * :math.pow(2, attempt - 1))

        Logger.debug(
          "Redis connection attempt #{attempt}/#{max_attempts} failed: #{inspect(reason)}, " <>
            "retrying in #{wait_time}ms"
        )

        Process.sleep(wait_time)
        do_wait_for_redis(redis_config, timeout, max_attempts, backoff, attempt + 1)
    end
  end

  @doc """
  Checks Redis health by attempting a PING command with a timeout.

  ## Parameters
  - `redis_config` - Keyword list with Redis connection config (host, port, password)
  - `timeout_ms` - Maximum time to wait for response in milliseconds

  ## Returns
  - `:ok` if Redis responds to PONG
  - `{:error, reason}` if connection fails or times out
  """
  @spec check_health(keyword(), pos_integer()) :: :ok | {:error, term()}
  def check_health(redis_config, timeout_ms \\ 2_000) do
    host = Keyword.get(redis_config, :host, "localhost")
    port = Keyword.get(redis_config, :port, 6379)
    password = Keyword.get(redis_config, :password)

    # Build connection options
    conn_opts = [
      host: host,
      port: port,
      timeout: timeout_ms,
      socket_opts: [keepalive: true]
    ]

    conn_opts =
      if password && password != "" do
        Keyword.put(conn_opts, :password, password)
      else
        conn_opts
      end

    # Start a temporary connection for health check
    case Redix.start_link(conn_opts) do
      {:ok, conn} ->
        result =
          case Redix.command(conn, ["PING"], timeout: timeout_ms) do
            {:ok, "PONG"} -> :ok
            {:error, reason} -> {:error, reason}
          end

        Redix.stop(conn)
        result

      {:error, reason} ->
        Logger.debug("Redis health check connection failed: #{inspect(reason)}")
        {:error, reason}
    end
  rescue
    error ->
      Logger.debug("Redis health check error: #{inspect(error)}")
      {:error, error}
  catch
    :exit, reason ->
      Logger.debug("Redis health check exit: #{inspect(reason)}")
      {:error, reason}
  end
end
