defmodule WhoNeedHelpWeb.GoogleAuthController do
  use WhoNeedHelpWeb, :controller

  require Logger

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

  import WhoNeedHelpWeb.UserAuth, only: [require_sudo_mode: 2]

  @session_key "google_auth_flow"
  @supported_locales ~w(en uk ru)

  plug :put_no_store
  plug :require_sudo_mode when action in [:start_link, :disconnect]

  def start_login(conn, _params) do
    start_flow(conn, "login", %{}, ~p"/users/log-in")
  end

  def start_registration(conn, %{"google_registration" => params}) when is_map(params) do
    locale = normalize_locale(params["locale"])
    terms_accepted = params["terms_accepted"] in [true, "true", "on", "1"]

    if terms_accepted do
      start_flow(
        conn,
        "register",
        %{"locale" => locale, "terms_accepted" => true},
        ~p"/users/register"
      )
    else
      conn
      |> put_flash(
        :error,
        gettext("Confirm that you are 18 or older and accept the safety rules first.")
      )
      |> redirect(to: ~p"/users/register")
    end
  end

  def start_registration(conn, _params) do
    conn
    |> put_flash(:error, gettext("The Google registration form is invalid."))
    |> redirect(to: ~p"/users/register")
  end

  def start_link(conn, _params) do
    user = conn.assigns.current_scope.user
    start_flow(conn, "link", %{"user_id" => user.id}, ~p"/users/settings")
  end

  def disconnect(conn, _params) do
    current_user = conn.assigns.current_scope.user

    case unlink_google_identity_and_audit(current_user) do
      {:ok, _identity} ->
        conn
        |> put_flash(:info, gettext("Google sign-in disconnected."))
        |> redirect(to: ~p"/users/settings")

      {:error, :not_found} ->
        conn
        |> put_flash(:error, gettext("No Google account is connected."))
        |> redirect(to: ~p"/users/settings")

      {:error, _reason} ->
        google_account_unavailable(conn, ~p"/users/settings")
    end
  end

  def callback(conn, params) do
    flow = get_session(conn, @session_key)
    conn = delete_session(conn, @session_key)

    with %{"flow" => flow_name, "session_params" => session_params} <- flow,
         true <- flow_name in ["login", "register", "link"],
         {:ok, identity_attrs} <-
           GoogleAuth.callback(callback_url(conn), params, session_params) do
      finish_flow(conn, flow_name, flow, identity_attrs)
    else
      nil ->
        callback_error(
          conn,
          flow,
          gettext("Google sign-in session expired. Please start again.")
        )

      false ->
        callback_error(conn, flow, gettext("Google sign-in session is invalid."))

      {:error, :email_not_verified} ->
        callback_error(
          conn,
          flow,
          gettext("Google did not provide a verified email address.")
        )

      {:error, error} ->
        Logger.warning("Google OAuth callback failed (#{error_name(error)})")
        callback_error(conn, flow, gettext("Google sign-in failed. Please try again."))
    end
  end

  defp start_flow(conn, flow, extra, failure_path) do
    if GoogleAuth.enabled?() do
      case GoogleAuth.authorize_url(callback_url(conn)) do
        {:ok, %{url: url, session_params: session_params}} ->
          session =
            extra
            |> Map.put("flow", flow)
            |> Map.put("session_params", session_params)

          conn
          |> put_session(@session_key, session)
          |> redirect(external: url)

        {:error, error} ->
          Logger.warning("Unable to start Google OAuth (#{error_name(error)})")

          conn
          |> put_flash(:error, gettext("Google sign-in could not be started."))
          |> redirect(to: failure_path)
      end
    else
      conn
      |> put_flash(:error, gettext("Google sign-in is not configured yet."))
      |> redirect(to: failure_path)
    end
  end

  defp finish_flow(conn, "login", _flow, identity_attrs) do
    case Accounts.login_user_by_google(identity_attrs) do
      {:ok, user} ->
        conn
        |> put_flash(:info, gettext("Welcome back!"))
        |> UserAuth.log_in_user(user)

      {:error, :not_linked} ->
        conn
        |> put_flash(
          :error,
          gettext(
            "This Google account is not connected yet. Create an account or sign in by email and connect Google in account settings."
          )
        )
        |> redirect(to: ~p"/users/log-in")

      {:error, _reason} ->
        google_account_unavailable(conn, ~p"/users/log-in")
    end
  end

  defp finish_flow(conn, "register", flow, identity_attrs) do
    with {:ok, _limit} <- RateLimiter.check(:registration_email, identity_attrs.email),
         {:ok, {user, _identity}} <-
           Accounts.register_user_by_google(identity_attrs, %{
             "locale" => flow["locale"],
             "terms_accepted" => flow["terms_accepted"]
           }) do
      conn
      |> put_flash(:info, gettext("Your account was created with Google."))
      |> UserAuth.log_in_user(user)
    else
      {:error, :email_already_registered} ->
        conn
        |> put_flash(
          :error,
          gettext(
            "An account with this email already exists. Sign in by email or password, then connect Google in account settings."
          )
        )
        |> redirect(to: ~p"/users/log-in")

      {:error, :rate_limited} ->
        conn
        |> put_flash(
          :error,
          gettext("Too many registration attempts in the configured time window.")
        )
        |> redirect(to: ~p"/users/register")

      {:error, _reason} ->
        google_account_unavailable(conn, ~p"/users/register")
    end
  end

  defp finish_flow(conn, "link", flow, identity_attrs) do
    current_user = get_in(conn.assigns, [:current_scope, Access.key(:user)])

    if current_user && flow["user_id"] == current_user.id &&
         Accounts.sudo_mode?(current_user, -10) do
      case link_google_identity_and_audit(current_user, identity_attrs) do
        {:ok, _identity} ->
          conn
          |> put_flash(:info, gettext("Google sign-in connected."))
          |> redirect(to: ~p"/users/settings")

        {:error, :already_linked} ->
          conn
          |> put_flash(:error, gettext("That Google account belongs to another local account."))
          |> redirect(to: ~p"/users/settings")

        {:error, :provider_already_linked} ->
          conn
          |> put_flash(:error, gettext("A different Google account is already connected."))
          |> redirect(to: ~p"/users/settings")

        {:error, _reason} ->
          google_account_unavailable(conn, ~p"/users/settings")
      end
    else
      conn
      |> put_flash(:error, gettext("Google account connection session is invalid."))
      |> redirect(to: ~p"/users/log-in")
    end
  end

  defp link_google_identity_and_audit(current_user, identity_attrs) do
    Repo.transact(fn ->
      with {:ok, identity} <- Accounts.link_google_identity(current_user, identity_attrs),
           {:ok, _audit} <-
             Trust.audit(
               current_user.id,
               "auth_identity.connected",
               "auth_identity",
               identity.id,
               %{"provider" => "google"}
             ) do
        {:ok, identity}
      end
    end)
  end

  defp unlink_google_identity_and_audit(current_user) do
    Repo.transact(fn ->
      with {:ok, identity} <- Accounts.unlink_google_identity(current_user),
           {:ok, _audit} <-
             Trust.audit(
               current_user.id,
               "auth_identity.disconnected",
               "auth_identity",
               identity.id,
               %{"provider" => "google"}
             ) do
        {:ok, identity}
      end
    end)
  end

  defp callback_error(conn, flow, message) do
    conn
    |> put_flash(:error, message)
    |> redirect(to: failure_path(flow))
  end

  defp google_account_unavailable(conn, path) do
    conn
    |> put_flash(:error, gettext("This Google account cannot be used right now."))
    |> redirect(to: path)
  end

  defp failure_path(%{"flow" => "register"}), do: ~p"/users/register"
  defp failure_path(%{"flow" => "link"}), do: ~p"/users/settings"
  defp failure_path(_flow), do: ~p"/users/log-in"

  defp callback_url(conn), do: url(conn, ~p"/auth/google/callback")

  defp normalize_locale(locale) when locale in @supported_locales, do: locale
  defp normalize_locale(_locale), do: "en"

  defp error_name(error) when is_atom(error), do: Atom.to_string(error)
  defp error_name(%{__struct__: module}), do: inspect(module)
  defp error_name(_error), do: "unknown_error"

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