defmodule Microwaveprop.BeaconMonitors do
  @moduledoc """
  The BeaconMonitors context: manages the monitor stations a user
  has registered. Each monitor has a unique random token the remote
  program uses to authenticate its reports.
  """

  import Ecto.Query

  alias Microwaveprop.Accounts.User
  alias Microwaveprop.BeaconMonitors.BeaconMonitor
  alias Microwaveprop.Repo

  @token_bytes 32

  @doc """
  Returns all monitors for the given user, newest first.
  """
  @spec list_monitors_for_user(User.t()) :: [BeaconMonitor.t()]
  def list_monitors_for_user(%User{id: user_id}) do
    Repo.all(
      from m in BeaconMonitor,
        where: m.user_id == ^user_id,
        order_by: [desc: m.inserted_at]
    )
  end

  @doc """
  Creates a new monitor for the given user with a freshly generated token.
  """
  @spec create_monitor(User.t(), map()) :: {:ok, BeaconMonitor.t()} | {:error, Ecto.Changeset.t()}
  def create_monitor(%User{} = user, attrs) do
    %BeaconMonitor{user_id: user.id, token: generate_token()}
    |> BeaconMonitor.changeset(attrs)
    |> Repo.insert()
  end

  @doc """
  Deletes a monitor owned by the given user.

  Returns `{:error, :not_found}` if the monitor does not exist or
  belongs to another user.
  """
  @spec delete_monitor(User.t(), Ecto.UUID.t()) ::
          {:ok, BeaconMonitor.t()} | {:error, :not_found} | {:error, Ecto.Changeset.t()}
  def delete_monitor(%User{id: user_id}, monitor_id) do
    query =
      from m in BeaconMonitor,
        where: m.id == ^monitor_id and m.user_id == ^user_id

    case Repo.one(query) do
      nil -> {:error, :not_found}
      monitor -> Repo.delete(monitor)
    end
  rescue
    Ecto.Query.CastError -> {:error, :not_found}
  end

  @doc """
  Looks up a monitor by its token. Returns nil if not found.
  """
  @spec get_monitor_by_token(String.t()) :: BeaconMonitor.t() | nil
  def get_monitor_by_token(token) when is_binary(token) do
    Repo.get_by(BeaconMonitor, token: token)
  end

  @doc """
  Returns a blank changeset for rendering the new-monitor form.
  """
  @spec change_monitor(map()) :: Ecto.Changeset.t()
  def change_monitor(attrs \\ %{}) do
    BeaconMonitor.changeset(%BeaconMonitor{}, attrs)
  end

  defp generate_token do
    @token_bytes
    |> :crypto.strong_rand_bytes()
    |> Base.url_encode64(padding: false)
  end
end
