defmodule MvWeb.RoleLive.Form do
  @moduledoc """
  LiveView form for creating and editing roles.

  ## Features
  - Create new roles
  - Edit existing roles (name, description, permission_set_name)
  - Custom dropdown for permission_set_name with badges
  - Form validation

  ## Security
  Only admins can access this page (enforced by authorization).
  """
  use MvWeb, :live_view

  alias Mv.Authorization.PermissionSets

  import MvWeb.RoleLive.Helpers, only: [format_error: 1]

  @impl true
  def render(assigns) do
    ~H"""
    <Layouts.app flash={@flash} current_user={@current_user}>
      <.header>
        {@page_title}
        <:subtitle>{gettext("Use this form to manage roles in your database.")}</:subtitle>
      </.header>

      <.form class="max-w-xl" for={@form} id="role-form" phx-change="validate" phx-submit="save">
        <.input field={@form[:name]} type="text" label={gettext("Name")} required />

        <.input
          field={@form[:description]}
          type="textarea"
          label={gettext("Description")}
          rows="3"
        />

        <div class="form-control">
          <label class="label" for="role-form_permission_set_name">
            <span class="label-text font-semibold">
              {gettext("Permission Set")}
              <span class="text-red-700">*</span>
            </span>
          </label>
          <select
            class={[
              "select select-bordered w-full",
              @form.errors[:permission_set_name] && "select-error"
            ]}
            name="role[permission_set_name]"
            id="role-form_permission_set_name"
            required
            aria-label={gettext("Permission Set")}
          >
            <option value="">{gettext("Select permission set")}</option>
            <%= for permission_set <- all_permission_sets() do %>
              <option
                value={permission_set}
                selected={@form[:permission_set_name].value == permission_set}
              >
                {format_permission_set_option(permission_set)}
              </option>
            <% end %>
          </select>
          <%= if @form.errors[:permission_set_name] do %>
            <%= for error <- List.wrap(@form.errors[:permission_set_name]) do %>
              <% {msg, _opts} = if is_tuple(error), do: error, else: {error, []} %>
              <p class="mt-1.5 flex gap-2 items-center text-sm text-error">
                <.icon name="hero-exclamation-circle" class="size-5" />
                {msg}
              </p>
            <% end %>
          <% end %>
        </div>

        <div class="mt-4">
          <.button phx-disable-with={gettext("Saving...")} variant="primary" type="submit">
            {gettext("Save Role")}
          </.button>
          <.button navigate={return_path(@return_to, @role)} type="button">
            {gettext("Cancel")}
          </.button>
        </div>
      </.form>
    </Layouts.app>
    """
  end

  @impl true
  def mount(params, _session, socket) do
    case params["id"] do
      nil ->
        action = gettext("New")
        page_title = action <> " " <> gettext("Role")

        {:ok,
         socket
         |> assign(:return_to, return_to(params["return_to"]))
         |> assign(:role, nil)
         |> assign(:page_title, page_title)
         |> assign_form()}

      id ->
        try do
          case Ash.get(
                 Mv.Authorization.Role,
                 id,
                 domain: Mv.Authorization,
                 actor: socket.assigns[:current_user]
               ) do
            {:ok, role} ->
              action = gettext("Edit")
              page_title = action <> " " <> gettext("Role")

              {:ok,
               socket
               |> assign(:return_to, return_to(params["return_to"]))
               |> assign(:role, role)
               |> assign(:page_title, page_title)
               |> assign_form()}

            {:error, %Ash.Error.Invalid{errors: [%Ash.Error.Query.NotFound{} | _]}} ->
              {:ok,
               socket
               |> put_flash(:error, gettext("Role not found."))
               |> redirect(to: ~p"/admin/roles")}

            {:error, error} ->
              {:ok,
               socket
               |> put_flash(:error, format_error(error))
               |> redirect(to: ~p"/admin/roles")}
          end
        rescue
          e in [Ash.Error.Invalid] ->
            # Handle exceptions that Ash.get might throw (e.g., policy violations)
            case e do
              %Ash.Error.Invalid{errors: [%Ash.Error.Query.NotFound{} | _]} ->
                {:ok,
                 socket
                 |> put_flash(:error, gettext("Role not found."))
                 |> redirect(to: ~p"/admin/roles")}

              _ ->
                reraise e, __STACKTRACE__
            end
        end
    end
  end

  @spec return_to(String.t() | nil) :: String.t()
  defp return_to("show"), do: "show"
  defp return_to(_), do: "index"

  @impl true
  def handle_event("validate", %{"role" => role_params}, socket) do
    validated_form = AshPhoenix.Form.validate(socket.assigns.form, role_params)
    {:noreply, assign(socket, form: validated_form)}
  end

  def handle_event("save", %{"role" => role_params}, socket) do
    actor = MvWeb.LiveHelpers.current_actor(socket)

    case MvWeb.LiveHelpers.submit_form(socket.assigns.form, role_params, actor) do
      {:ok, role} ->
        notify_parent({:saved, role})

        redirect_path =
          if socket.assigns.return_to == "show" do
            ~p"/admin/roles/#{role.id}"
          else
            ~p"/admin/roles"
          end

        socket =
          socket
          |> put_flash(:info, gettext("Role saved successfully."))
          |> push_navigate(to: redirect_path)

        {:noreply, socket}

      {:error, form} ->
        {:noreply, assign(socket, form: form)}
    end
  end

  @spec notify_parent(any()) :: any()
  defp notify_parent(msg), do: send(self(), {__MODULE__, msg})

  @spec assign_form(Phoenix.LiveView.Socket.t()) :: Phoenix.LiveView.Socket.t()
  defp assign_form(%{assigns: %{role: role, current_user: actor}} = socket) do
    form =
      if role do
        AshPhoenix.Form.for_update(role, :update_role,
          domain: Mv.Authorization,
          as: "role",
          actor: actor
        )
      else
        AshPhoenix.Form.for_create(
          Mv.Authorization.Role,
          :create_role,
          domain: Mv.Authorization,
          as: "role",
          actor: actor
        )
      end

    assign(socket, form: to_form(form))
  end

  defp all_permission_sets do
    PermissionSets.all_permission_sets() |> Enum.map(&Atom.to_string/1)
  end

  defp format_permission_set_option("own_data"),
    do: gettext("own_data - Access only to own data")

  defp format_permission_set_option("read_only"),
    do: gettext("read_only - Read access to all data")

  defp format_permission_set_option("normal_user"),
    do: gettext("normal_user - Create/Read/Update access")

  defp format_permission_set_option("admin"),
    do: gettext("admin - Unrestricted access")

  defp format_permission_set_option(set), do: set

  @spec return_path(String.t(), Mv.Authorization.Role.t() | nil) :: String.t()
  defp return_path("index", _role), do: ~p"/admin/roles"
  defp return_path("show", role) when not is_nil(role), do: ~p"/admin/roles/#{role.id}"
  defp return_path("show", _role), do: ~p"/admin/roles"
  defp return_path(_, role) when not is_nil(role), do: ~p"/admin/roles/#{role.id}"
  defp return_path(_, _role), do: ~p"/admin/roles"
end
