defmodule ToweropsWeb.Permissions do
  @moduledoc """
  Authorization helpers for checking user permissions in LiveViews, controllers, and templates.

  Integrates with `Towerops.Organizations.Policy` to provide a simple, reusable API
  for permission checks based on organization membership roles.

  ## Usage in LiveViews

      import ToweropsWeb.Permissions

      def handle_event("delete_backup", _params, socket) do
        if can?(socket, :delete, :backup) do
          # Delete backup
        else
          {:noreply, put_flash(socket, :error, "You don't have permission to delete backups")}
        end
      end

  ## Usage in Templates

      <%= if can?(@current_scope, :delete, :backup) do %>
        <button phx-click="delete_backup">Delete</button>
      <% end %>

  ## Usage in Controllers

      import ToweropsWeb.Permissions

      def delete(conn, _params) do
        if can?(conn.assigns.current_scope, :delete, :backup) do
          # Delete backup
        else
          conn
          |> put_flash(:error, "You don't have permission to delete backups")
          |> redirect(to: ~p"/")
        end
      end
  """

  alias Phoenix.LiveView.Socket
  alias Towerops.Accounts.Scope
  alias Towerops.Organizations.Policy

  @doc """
  Checks if the current user can perform an action on a resource.

  Accepts either a socket (LiveView) or a Scope struct directly.

  Returns `true` if:
  - User is a superuser (can do everything)
  - User's organization membership role allows the action per Policy

  ## Examples

      iex> can?(socket, :delete, :backup)
      true

      iex> can?(%Scope{}, :edit, :device)
      false
  """
  def can?(%Socket{} = socket, action, resource) do
    can?(socket.assigns.current_scope, action, resource)
  end

  def can?(%Scope{} = scope, action, resource) do
    if Scope.superuser?(scope), do: true, else: do_can?(scope, action, resource)
  end

  def can?(nil, _action, _resource), do: false

  @doc """
  Checks if the current user is an organization owner.

  Accepts either a socket (LiveView) or a Scope struct directly.

  ## Examples

      iex> owner?(socket)
      true

      iex> owner?(%Scope{})
      false
  """
  def owner?(%Socket{} = socket) do
    owner?(socket.assigns.current_scope)
  end

  def owner?(%Scope{} = scope) do
    if Scope.superuser?(scope), do: true, else: do_owner?(scope)
  end

  def owner?(nil), do: false

  @doc """
  Checks if the current user is an organization owner or admin.

  Accepts either a socket (LiveView) or a Scope struct directly.

  ## Examples

      iex> admin?(socket)
      true

      iex> admin?(%Scope{})
      false
  """
  def admin?(%Socket{} = socket) do
    admin?(socket.assigns.current_scope)
  end

  def admin?(%Scope{} = scope) do
    if Scope.superuser?(scope), do: true, else: do_admin?(scope)
  end

  def admin?(nil), do: false

  # Pattern-matched private helpers for permission checks

  defp do_can?(%Scope{organization: nil}, _action, _resource), do: false

  defp do_can?(%Scope{} = scope, action, resource) do
    scope |> get_membership() |> Policy.can?(action, resource)
  end

  defp do_owner?(%Scope{organization: nil}), do: false

  defp do_owner?(%Scope{} = scope) do
    membership = get_membership(scope)
    membership && membership.role == :owner
  end

  defp do_admin?(%Scope{organization: nil}), do: false

  defp do_admin?(%Scope{} = scope) do
    membership = get_membership(scope)
    membership && membership.role in [:owner, :admin]
  end

  defp get_membership(%Scope{organization: org, user: user}) when not is_nil(org) and not is_nil(user) do
    if Ecto.assoc_loaded?(org.memberships) do
      Enum.find(org.memberships, &(&1.user_id == user.id))
    end
  end

  defp get_membership(_scope), do: nil
end
