# SPDX-FileCopyrightText: 2022 Alembic Pty Ltd
#
# SPDX-License-Identifier: MIT

defmodule AshAuthentication.Strategy.Password.Plug do
  @moduledoc """
  Plugs for the password strategy.

  Handles registration, sign-in and password resets.
  """

  alias AshAuthentication.{Info, Strategy, Strategy.Password}
  alias Plug.Conn
  import Ash.PlugHelpers, only: [get_actor: 1, get_tenant: 1, get_context: 1]
  import AshAuthentication.Plug.Helpers, only: [store_authentication_result: 2]

  @doc "Handle a registration request"
  @spec register(Conn.t(), Password.t()) :: Conn.t()
  def register(conn, strategy) do
    params = subject_params(conn, strategy)
    opts = opts(conn)
    result = Strategy.action(strategy, :register, params, opts)
    store_authentication_result(conn, result)
  end

  @doc "Handle a sign-in request"
  @spec sign_in(Conn.t(), Password.t()) :: Conn.t()
  def sign_in(conn, strategy) do
    params = subject_params(conn, strategy)
    opts = opts(conn)
    result = Strategy.action(strategy, :sign_in, params, opts)
    store_authentication_result(conn, result)
  end

  @doc "Handle a request to validate a sign in token"
  @spec sign_in_with_token(Conn.t(), Password.t()) :: Conn.t()
  def sign_in_with_token(conn, strategy) do
    params = conn.params
    opts = opts(conn)
    result = Strategy.action(strategy, :sign_in_with_token, params, opts)
    store_authentication_result(conn, result)
  end

  @doc "Handle a reset request request"
  @spec reset_request(Conn.t(), Password.t()) :: Conn.t()
  def reset_request(conn, strategy) do
    params = subject_params(conn, strategy)
    opts = opts(conn)
    result = Strategy.action(strategy, :reset_request, params, opts)
    store_authentication_result(conn, result)
  end

  @doc "Handle a reset request"
  @spec reset(Conn.t(), Password.t()) :: Conn.t()
  def reset(conn, strategy) do
    get_tenant(conn)
    params = subject_params(conn, strategy)
    opts = opts(conn)
    result = Strategy.action(strategy, :reset, params, opts)
    store_authentication_result(conn, result)
  end

  defp subject_params(conn, strategy) do
    subject_name =
      strategy.resource
      |> Info.authentication_subject_name!()
      |> to_string()

    Map.get(conn.params, subject_name, %{})
  end

  defp opts(conn) do
    [actor: get_actor(conn), tenant: get_tenant(conn), context: get_context(conn)]
    |> Enum.reject(&is_nil(elem(&1, 1)))
  end
end
