defmodule Guardian.DB.Token do
  @moduledoc """
  A very simple model for storing tokens generated by `Guardian`.
  """

  use Ecto.Schema
  import Ecto.Changeset

  alias Guardian.DB.Token

  @primary_key {:jti, :string, autogenerate: false}
  @required_fields ~w(jti aud)a
  @allowed_fields ~w(jti typ aud iss sub exp jwt claims)a

  schema "virtual: token" do
    field(:typ, :string)
    field(:aud, :string)
    field(:iss, :string)
    field(:sub, :string)
    field(:exp, :integer)
    field(:jwt, :string)
    field(:claims, :map)

    timestamps()
  end

  @doc """
  Find one token by matching jti and aud.
  """
  def find_by_claims(claims) do
    adapter().one(claims, config())
  end

  @doc """
  Create a new token based on the JWT and decoded claims.
  """
  def create(claims, jwt) do
    adapter().insert(changeset(claims, jwt), config())
  end

  @doc false
  def changeset(claims, jwt) do
    prepared_claims =
      claims
      |> Map.put("jwt", jwt)
      |> Map.put("claims", claims)

    %Token{}
    |> cast(prepared_claims, @allowed_fields)
    |> validate_required(@required_fields)
  end

  @doc """
  Purge any tokens that are expired. This should be done periodically to keep
  your DB table clean of clutter.
  """
  def purge_expired_tokens do
    timestamp = Guardian.timestamp()

    adapter().purge_expired_tokens(timestamp, config())
  end

  @doc false
  def destroy_by_sub(sub) do
    adapter().delete_by_sub(sub, config())
  end

  @doc false
  defp config do
    Application.fetch_env!(:guardian, Guardian.DB)
  end

  @doc false
  def destroy_token(nil, claims, jwt), do: {:ok, {claims, jwt}}

  def destroy_token(model, claims, jwt) do
    case adapter().delete(model, config()) do
      {:error, _} -> {:error, :could_not_revoke_token}
      nil -> {:error, :could_not_revoke_token}
      _ -> {:ok, {claims, jwt}}
    end
  end

  defp adapter do
    Keyword.get(config(), :adapter, Guardian.DB.EctoAdapter)
  end
end
