defmodule WhoNeedHelpWeb.UserSessionController do
  use WhoNeedHelpWeb, :controller

  require Logger

  alias WhoNeedHelp.{Accounts, GoogleAuth}
  alias WhoNeedHelp.Trust.RateLimiter
  alias WhoNeedHelpWeb.UserAuth

  plug :assign_magic_link_form
  plug :put_no_store

  def new(conn, _params) do
    email = get_in(conn.assigns, [:current_scope, Access.key(:user), Access.key(:email)])
    form = Phoenix.Component.to_form(%{"email" => email}, as: "user")

    render(conn, :new, form: form)
  end

  # magic link login
  def create(conn, %{"user" => %{"token" => token} = user_params} = params)
      when is_binary(token) do
    info =
      case params do
        %{"_action" => "confirmed"} -> gettext("User confirmed successfully.")
        _ -> gettext("Welcome back!")
      end

    case Accounts.login_user_by_magic_link(token) do
      {:ok, {user, _expired_tokens}} ->
        conn
        |> put_flash(:info, info)
        |> UserAuth.log_in_user(user, user_params)

      {:error, :not_found} ->
        conn
        |> put_flash(:error, gettext("The link is invalid or it has expired."))
        |> render(:new, form: Phoenix.Component.to_form(%{}, as: "user"))
    end
  end

  # email + password login
  def create(conn, %{"user" => %{"email" => email, "password" => password} = user_params})
      when is_binary(email) and is_binary(password) do
    email_scope = email |> String.trim() |> String.downcase()

    case RateLimiter.check(:password_login_email, email_scope) do
      {:ok, _limit} ->
        if user = Accounts.get_user_by_email_and_password(email, password) do
          conn
          |> put_flash(:info, gettext("Welcome back!"))
          |> UserAuth.log_in_user(user, user_params)
        else
          invalid_password_response(conn, user_params)
        end

      {:error, :rate_limited} ->
        conn
        |> put_status(:too_many_requests)
        |> put_flash(:error, gettext("Too many sign-in attempts in the configured time window."))
        |> render(:new, form: Phoenix.Component.to_form(user_params, as: "user"))
    end
  end

  # magic link request
  def create(conn, %{"user" => %{"email" => email}}) when is_binary(email) do
    email_scope = email |> String.trim() |> String.downcase()

    case RateLimiter.check(:magic_link_email, email_scope) do
      {:ok, _limit} ->
        case Accounts.get_user_by_email(email) do
          %Accounts.User{moderation_status: status} = user when status != :suspended ->
            case Accounts.deliver_login_instructions(
                   user,
                   &"#{url(~p"/users/log-in")}#token=#{URI.encode_www_form(&1)}"
                 ) do
              {:ok, _email} ->
                :ok

              {:error, _reason} ->
                Logger.warning("Unable to send requested login instructions")
            end

          _missing_or_suspended ->
            :ok
        end

        info =
          gettext(
            "If your email is in our system, you will receive instructions for logging in shortly."
          )

        conn
        |> put_flash(:info, info)
        |> redirect(to: ~p"/users/log-in")

      {:error, :rate_limited} ->
        conn
        |> put_status(:too_many_requests)
        |> put_flash(
          :error,
          gettext("Too many sign-in emails in the configured time window.")
        )
        |> render(:new, form: Phoenix.Component.to_form(%{"email" => email}, as: "user"))
    end
  end

  def create(conn, _params) do
    conn
    |> put_status(:bad_request)
    |> put_flash(:error, gettext("The sign-in form is invalid."))
    |> render(:new, form: Phoenix.Component.to_form(%{}, as: "user"))
  end

  def delete(conn, _params) do
    conn
    |> put_flash(:info, gettext("Logged out successfully."))
    |> UserAuth.log_out_user()
  end

  defp invalid_password_response(conn, user_params) do
    # Keep the response identical for unknown accounts and incorrect passwords.
    conn
    |> put_flash(:error, gettext("Invalid email or password"))
    |> render(:new, form: Phoenix.Component.to_form(user_params, as: "user"))
  end

  defp assign_magic_link_form(conn, _opts) do
    conn
    |> assign(
      :magic_link_form,
      Phoenix.Component.to_form(%{"token" => ""}, as: "user")
    )
    |> assign(:google_auth_enabled, GoogleAuth.enabled?())
  end

  defp put_no_store(conn, _opts), do: put_resp_header(conn, "cache-control", "no-store")
end
