defmodule Guardian.DB.ETSAdapter do
  @moduledoc """
  Implement the Guardian.DB.Adapter for ETS
  """

  @behaviour Guardian.DB.Adapter

  @impl true
  def one(claims, opts) do
    jti = Map.get(claims, "jti")
    aud = Map.get(claims, "aud")

    match =
      opts
      |> Keyword.fetch!(:table)
      |> :ets.match({jti, aud, :_, :_, :"$1"})

    case match do
      [[token]] -> token
      _ -> nil
    end
  end

  @impl true
  def insert(%{valid?: true} = changeset, opts) do
    table = Keyword.fetch!(opts, :table)
    token = Map.merge(changeset.data, changeset.changes)

    unless :ets.insert(table, {token.jti, token.aud, token.sub, token.exp, token}) do
      raise """
      An error occurred trying to insert a new record into the ETS table #{table}.

      Please ensure you've created the table before attempting to insert records.
      """
    end

    {:ok, token}
  end

  def insert(changeset, _opts) do
    {:error, changeset}
  end

  @impl true
  def delete(%{jti: jti} = token, opts) do
    table = Keyword.fetch!(opts, :table)

    unless :ets.delete(table, jti) do
      raise """
      An error occurred trying to delete a record from the ETS table #{table}.

      Please ensure you've created the table before attempting to delete records.
      """
    end

    {:ok, token}
  end

  @impl true
  def delete_by_sub(sub, opts) do
    table = Keyword.fetch!(opts, :table)

    table
    |> :ets.match({:"$1", :_, sub, :_, :"$2"})
    |> delete_many(table)
  end

  @impl true
  def purge_expired_tokens(exp, opts) do
    table = Keyword.fetch!(opts, :table)
    matcher = [{{:"$1", :"$2", :"$3", :"$4", :"$5"}, [{:<, :"$4", exp}], [[:"$1", :"$5"]]}]

    table
    |> :ets.select(matcher)
    |> delete_many(table)
  end

  defp delete_many(tokens, table) do
    deleted_tokens =
      Enum.reduce(tokens, [], fn [jti, token], acc ->
        if :ets.delete(table, jti) do
          [token | acc]
        else
          acc
        end
      end)

    {length(deleted_tokens), deleted_tokens}
  end
end
