defmodule Prymn.Servers do
  @moduledoc """
  The Servers context.
  """

  import Ecto.Query, warn: false
  alias Prymn.Repo

  alias Prymn.Servers.Server

  @doc """
  Returns the list of servers.

  ## Examples

      iex> list_servers()
      [%Server{}, ...]

  """
  def list_servers do
    Repo.all(Server |> order_by(desc: :inserted_at))
  end

  def list_registered_servers() do
    query = from s in Server, select: s, where: s.status == :registered
    Repo.all(query)
  end

  @doc """
  Gets a single server.

  Raises `Ecto.NoResultsError` if the Server does not exist.

  ## Examples

      iex> get_server!(123)
      %Server{}

      iex> get_server!(456)
      ** (Ecto.NoResultsError)

  """
  def get_server!(id), do: Repo.get!(Server, id)

  @doc """
  Get a single server by its IP.
  """
  @spec get_server_by_ip!(String.t()) :: Server.t()

  def get_server_by_ip!(ip), do: Repo.get_by!(Server, public_ip: ip)

  @doc """

  Start a new server connection with the app.
  """
  def create_server(attrs \\ %{}) do
    # Create a unique registration token
    %Server{registration_token: :crypto.strong_rand_bytes(16)}
    |> Server.changeset(attrs)
    |> Repo.insert()
  end

  @doc """
  Registers a server using a registration token.
  """
  def register_server(token, public_ip) do
    with true <- :inet.is_ip_address(public_ip),
         {:ok, token} <- Base.decode64(token) do
      public_ip_string =
        public_ip
        |> :inet.ntoa()
        |> to_string()

      from(s in Server, where: s.registration_token == ^token, select: s)
      |> Repo.one!()
      |> update_server(%{public_ip: public_ip_string, status: :registered})
    else
      false -> {:error, :invalid_ip}
      :error -> {:error, :bad_token}
    end
  end

  @doc """
  Updates a server.

  ## Examples

      iex> update_server(server, %{field: new_value})
      {:ok, %Server{}}

      iex> update_server(server, %{field: bad_value})
      {:error, %Ecto.Changeset{}}

  """
  def update_server(%Server{} = server, attrs) do
    server
    |> Server.changeset(attrs)
    |> Repo.update()
  end

  @doc """
  Deletes a server.

  ## Examples

      iex> delete_server(server)
      {:ok, %Server{}}

      iex> delete_server(server)
      {:error, %Ecto.Changeset{}}

  """
  def delete_server(%Server{} = server) do
    Repo.delete(server)
  end

  @doc """
  Returns an `%Ecto.Changeset{}` for tracking server changes.

  ## Examples

      iex> change_server(server)
      %Ecto.Changeset{data: %Server{}}

  """
  def change_server(%Server{} = server, attrs \\ %{}) do
    Server.changeset(server, attrs)
  end

  @doc """
  Returns a string containing the command that needs to be executed to the
  remote server in order to register it to the backend.
  """
  @spec create_setup_command(Server.t()) :: String.t()
  def create_setup_command(%Server{registration_token: token}) do
    token
    |> Base.encode64()
    |> then(fn token ->
      "wget -O- " <> PrymnWeb.Endpoint.url() <> "/install | sudo sh -s " <> token
    end)
  end
end