defmodule BerrypodWeb.UserSessionController do
  use BerrypodWeb, :controller

  alias Berrypod.Accounts
  alias BerrypodWeb.UserAuth

  plug BerrypodWeb.Plugs.RateLimit, [type: :login] when action in [:create, :verify_totp]

  def create(conn, %{"_action" => "confirmed"} = params) do
    create(conn, params, "User confirmed successfully.")
  end

  def create(conn, params) do
    create(conn, params, "Welcome back!")
  end

  # magic link login
  defp create(conn, %{"user" => %{"token" => token} = user_params} = params, info) do
    case Accounts.login_user_by_magic_link(token) do
      {:ok, {user, tokens_to_disconnect}} ->
        UserAuth.disconnect_sessions(tokens_to_disconnect)

        conn
        |> maybe_store_return_to(params)
        |> maybe_require_totp(user, user_params, info)

      _ ->
        conn
        |> put_flash(:error, "The link is invalid or it has expired.")
        |> redirect(to: ~p"/users/log-in")
    end
  end

  # email + password login
  defp create(conn, %{"user" => user_params} = params, info) do
    %{"email" => email, "password" => password} = user_params

    if user = Accounts.get_user_by_email_and_password(email, password) do
      conn
      |> maybe_store_return_to(params)
      |> maybe_require_totp(user, user_params, info)
    else
      # In order to prevent user enumeration attacks, don't disclose whether the email is registered.
      conn
      |> put_flash(:error, "Invalid email or password")
      |> put_flash(:email, String.slice(email, 0, 160))
      |> redirect(to: ~p"/users/log-in")
    end
  end

  defp maybe_store_return_to(conn, %{"return_to" => "/" <> _ = return_to}) do
    put_session(conn, :user_return_to, return_to)
  end

  defp maybe_store_return_to(conn, _params), do: conn

  defp maybe_require_totp(conn, user, user_params, info) do
    if Accounts.totp_enabled?(user) do
      remember_me = user_params["remember_me"] == "true"

      conn
      |> put_session(:totp_pending_user_id, user.id)
      |> put_session(:totp_pending_remember_me, remember_me)
      |> redirect(to: ~p"/users/totp")
    else
      conn
      |> put_flash(:info, info)
      |> UserAuth.log_in_user(user, user_params)
    end
  end

  def verify_totp(conn, %{"totp" => %{"code" => code}, "remember_me" => remember_me}) do
    user_id = get_session(conn, :totp_pending_user_id)

    if user_id do
      user = Accounts.get_user!(user_id)

      case Accounts.verify_totp(user, code) do
        :ok ->
          user_params = if remember_me == "true", do: %{"remember_me" => "true"}, else: %{}

          conn
          |> delete_session(:totp_pending_user_id)
          |> delete_session(:totp_pending_remember_me)
          |> put_flash(:info, "Welcome back!")
          |> UserAuth.log_in_user(user, user_params)

        :error ->
          conn
          |> put_flash(:error, "Invalid code. Please try again.")
          |> redirect(to: ~p"/users/totp")
      end
    else
      conn
      |> put_flash(:error, "Session expired. Please log in again.")
      |> redirect(to: ~p"/users/log-in")
    end
  end

  def update_password(conn, %{"user" => user_params} = params) do
    user = conn.assigns.current_scope.user
    true = Accounts.sudo_mode?(user)
    {:ok, {_user, expired_tokens}} = Accounts.update_user_password(user, user_params)

    # disconnect all existing LiveViews with old sessions
    UserAuth.disconnect_sessions(expired_tokens)

    conn
    |> put_session(:user_return_to, ~p"/admin/account")
    |> create(params, "Password updated successfully!")
  end

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