defmodule MvWeb.LinkOidcAccountLive do
  @moduledoc """
  LiveView for linking an OIDC account to an existing password account.

  This page is shown when a user tries to log in via OIDC using an email
  that already exists with a password-only account. The user must verify
  their password before the OIDC account can be linked.

  ## Flow
  1. User attempts OIDC login with email that has existing password account
  2. System raises `PasswordVerificationRequired` error
  3. AuthController redirects here with user_id and oidc_user_info in session
  4. User enters password to verify identity
  5. On success, oidc_id is linked to user account
  6. User is redirected to complete OIDC login
  """
  use MvWeb, :live_view
  require Ash.Query
  require Logger

  @impl true
  def mount(_params, session, socket) do
    # Use SystemActor for authorization during OIDC linking (user is not yet logged in)
    system_actor = Mv.Helpers.SystemActor.get_system_actor()

    with user_id when not is_nil(user_id) <- Map.get(session, "oidc_linking_user_id"),
         oidc_user_info when not is_nil(oidc_user_info) <-
           Map.get(session, "oidc_linking_user_info"),
         {:ok, user} <- Ash.get(Mv.Accounts.User, user_id, actor: system_actor) do
      # Check if user is passwordless
      if passwordless?(user) do
        # Auto-link passwordless user immediately
        {:ok, auto_link_passwordless_user(socket, user, oidc_user_info)}
      else
        # Show password form for password-protected user
        {:ok, initialize_socket(socket, user, oidc_user_info)}
      end
    else
      nil ->
        {:ok, redirect_with_error(socket, dgettext("auth", "Invalid session. Please try again."))}

      {:error, _} ->
        {:ok, redirect_with_error(socket, dgettext("auth", "Session expired. Please try again."))}
    end
  end

  defp passwordless?(user) do
    is_nil(user.hashed_password)
  end

  defp reload_user!(user_id) do
    # Use SystemActor for authorization during OIDC linking (user is not yet logged in)
    system_actor = Mv.Helpers.SystemActor.get_system_actor()

    Mv.Accounts.User
    |> Ash.Query.filter(id == ^user_id)
    |> Ash.read_one!(actor: system_actor)
  end

  defp reset_password_form(socket) do
    assign(socket, :form, to_form(%{"password" => ""}))
  end

  defp auto_link_passwordless_user(socket, user, oidc_user_info) do
    oidc_id = Map.get(oidc_user_info, "sub") || Map.get(oidc_user_info, "id")

    # Use SystemActor for authorization (passwordless user auto-linking)
    system_actor = Mv.Helpers.SystemActor.get_system_actor()

    case user.id
         |> reload_user!()
         |> Ash.Changeset.for_update(:link_oidc_id, %{
           oidc_id: oidc_id,
           oidc_user_info: oidc_user_info
         })
         |> Ash.update(actor: system_actor) do
      {:ok, updated_user} ->
        Logger.info(
          "Passwordless account auto-linked to OIDC: user_id=#{updated_user.id}, oidc_id=#{oidc_id}"
        )

        socket
        |> put_flash(
          :success,
          dgettext("auth", "Account activated! Redirecting to complete sign-in...")
        )
        |> Phoenix.LiveView.redirect(to: ~p"/auth/user/oidc")

      {:error, error} ->
        Logger.warning(
          "Failed to auto-link passwordless account: user_id=#{user.id}, error=#{inspect(error)}"
        )

        error_message = extract_user_friendly_error(error)

        socket
        |> put_flash(:error, error_message)
        |> redirect(to: ~p"/sign-in")
    end
  end

  defp extract_user_friendly_error(%Ash.Error.Invalid{errors: errors}) do
    # Check for specific error types
    Enum.find_value(errors, fn
      %Ash.Error.Changes.InvalidAttribute{field: :oidc_id, message: message} ->
        if String.contains?(message, "already been taken") do
          dgettext(
            "auth",
            "This OIDC account is already linked to another user. Please contact support."
          )
        else
          nil
        end

      %Ash.Error.Changes.InvalidAttribute{field: :email, message: message} ->
        if String.contains?(message, "already been taken") do
          dgettext(
            "auth",
            "The email address from your OIDC provider is already registered to another account. Please change your email in the identity provider or contact support."
          )
        else
          nil
        end

      _ ->
        nil
    end) ||
      dgettext("auth", "Failed to link account. Please try again or contact support.")
  end

  defp extract_user_friendly_error(_error) do
    dgettext("auth", "Failed to link account. Please try again or contact support.")
  end

  defp initialize_socket(socket, user, oidc_user_info) do
    socket
    |> assign(:user, user)
    |> assign(:oidc_user_info, oidc_user_info)
    |> assign(:password, "")
    |> assign(:error, nil)
    |> reset_password_form()
  end

  defp redirect_with_error(socket, message) do
    socket
    |> put_flash(:error, message)
    |> redirect(to: ~p"/sign-in")
  end

  @impl true
  def handle_event("validate", %{"password" => password}, socket) do
    {:noreply, assign(socket, :password, password)}
  end

  @impl true
  def handle_event("submit", %{"password" => password}, socket) do
    user = socket.assigns.user
    oidc_user_info = socket.assigns.oidc_user_info

    # Verify the password using AshAuthentication
    case verify_password(user.email, password) do
      {:ok, verified_user} ->
        # Password correct - link the OIDC account
        link_oidc_account(socket, verified_user, oidc_user_info)

      {:error, _reason} ->
        # Password incorrect - log security event
        Logger.warning("Failed password verification for OIDC linking: user_email=#{user.email}")

        {:noreply,
         socket
         |> assign(:error, dgettext("auth", "Incorrect password. Please try again."))
         |> reset_password_form()}
    end
  end

  defp verify_password(email, password) do
    # Use AshAuthentication password strategy to verify
    strategies = AshAuthentication.Info.authentication_strategies(Mv.Accounts.User)
    password_strategy = Enum.find(strategies, fn s -> s.name == :password end)

    if password_strategy do
      AshAuthentication.Strategy.Password.Actions.sign_in(
        password_strategy,
        %{
          "email" => email,
          "password" => password
        },
        []
      )
    else
      {:error, "Password authentication not configured"}
    end
  end

  defp link_oidc_account(socket, user, oidc_user_info) do
    oidc_id = Map.get(oidc_user_info, "sub") || Map.get(oidc_user_info, "id")

    # Use SystemActor for authorization (user just verified password but is not yet logged in)
    system_actor = Mv.Helpers.SystemActor.get_system_actor()

    # Update the user with the OIDC ID
    case user.id
         |> reload_user!()
         |> Ash.Changeset.for_update(:link_oidc_id, %{
           oidc_id: oidc_id,
           oidc_user_info: oidc_user_info
         })
         |> Ash.update(actor: system_actor) do
      {:ok, updated_user} ->
        # After successful linking, redirect to OIDC login
        # Since the user now has an oidc_id, the next OIDC login will succeed
        Logger.info(
          "OIDC account successfully linked after password verification: user_id=#{updated_user.id}, oidc_id=#{oidc_id}"
        )

        {:noreply,
         socket
         |> put_flash(
           :success,
           dgettext(
             "auth",
             "Your OIDC account has been successfully linked! Redirecting to complete sign-in..."
           )
         )
         |> Phoenix.LiveView.redirect(to: ~p"/auth/user/oidc")}

      {:error, error} ->
        Logger.warning(
          "Failed to link OIDC account after password verification: user_id=#{user.id}, error=#{inspect(error)}"
        )

        error_message = extract_user_friendly_error(error)

        {:noreply,
         socket
         |> assign(:error, error_message)
         |> reset_password_form()}
    end
  end

  @impl true
  def render(assigns) do
    ~H"""
    <div class="mx-auto max-w-sm mt-16">
      <%!-- Language Selector --%>
      <nav aria-label={dgettext("auth", "Language selection")} class="flex justify-center mb-4">
        <form method="post" action="/set_locale" class="text-sm">
          <input type="hidden" name="_csrf_token" value={Plug.CSRFProtection.get_csrf_token()} />
          <select
            name="locale"
            onchange="this.form.submit()"
            class="select select-sm select-bordered"
            aria-label={dgettext("auth", "Select language")}
          >
            <option value="de" selected={Gettext.get_locale() == "de"}>🇩🇪 Deutsch</option>
            <option value="en" selected={Gettext.get_locale() == "en"}>🇬🇧 English</option>
          </select>
        </form>
      </nav>

      <main>
        <.header class="text-center">
          {dgettext("auth", "Link OIDC Account")}
          <:subtitle>
            {dgettext(
              "auth",
              "An account with email %{email} already exists. Please enter your password to link your OIDC account.",
              email: @user.email
            )}
          </:subtitle>
        </.header>

        <.form for={@form} id="link-oidc-form" phx-submit="submit" phx-change="validate" class="mt-8">
          <div class="space-y-6">
            <div>
              <.input
                field={@form[:password]}
                type="password"
                label={dgettext("auth", "Password")}
                required
              />
            </div>

            <%= if @error do %>
              <div class="rounded-md bg-red-50 p-4">
                <p class="text-sm text-red-800">{@error}</p>
              </div>
            <% end %>

            <div>
              <.button phx-disable-with={dgettext("auth", "Linking...")} class="w-full">
                {dgettext("auth", "Link Account")}
              </.button>
            </div>
          </div>
        </.form>

        <div class="mt-4 text-center text-sm">
          <.link navigate={~p"/sign-in"} class="text-brand hover:underline">
            {dgettext("auth", "Cancel")}
          </.link>
        </div>
      </main>
    </div>
    """
  end
end
