defmodule Mv.Fixtures do
  @moduledoc """
  Shared test fixtures for consistent test data creation.

  This module provides factory functions for creating test data across
  different test suites, ensuring consistency and reducing duplication.
  """

  @doc """
  Creates a member with default or custom attributes.

  Uses system_actor for authorization to bypass permission checks in tests.

  ## Parameters
    - `attrs` - Map or keyword list of attributes to override defaults

  ## Returns
    - Member struct

  ## Examples

      iex> member_fixture()
      %Mv.Membership.Member{first_name: "Test", ...}

      iex> member_fixture(%{first_name: "Alice", email: "alice@example.com"})
      %Mv.Membership.Member{first_name: "Alice", email: "alice@example.com"}

  """
  def member_fixture(attrs \\ %{}) do
    system_actor = Mv.Helpers.SystemActor.get_system_actor()

    attrs
    |> Enum.into(%{
      first_name: "Test",
      last_name: "Member",
      email: "test#{System.unique_integer([:positive])}@example.com"
    })
    |> Mv.Membership.create_member(actor: system_actor)
    |> case do
      {:ok, member} -> member
      {:error, error} -> raise "Failed to create member: #{inspect(error)}"
    end
  end

  @doc """
  Creates a user with default or custom attributes.

  Uses system_actor for authorization to bypass permission checks in tests.

  Note: create_user action should work via AshAuthentication bypass,
  but we use system_actor for consistency and safety.

  ## Parameters
    - `attrs` - Map or keyword list of attributes to override defaults

  ## Returns
    - User struct

  ## Examples

      iex> user_fixture()
      %Mv.Accounts.User{email: "user123@example.com"}

      iex> user_fixture(%{email: "custom@example.com"})
      %Mv.Accounts.User{email: "custom@example.com"}

  """
  def user_fixture(attrs \\ %{}) do
    system_actor = Mv.Helpers.SystemActor.get_system_actor()

    attrs
    |> Enum.into(%{
      email: "user#{System.unique_integer([:positive])}@example.com"
    })
    |> Mv.Accounts.create_user(actor: system_actor)
    |> case do
      {:ok, user} -> user
      {:error, error} -> raise "Failed to create user: #{inspect(error)}"
    end
  end

  @doc """
  Creates a user linked to a member.

  ## Parameters
    - `user_attrs` - Map or keyword list of user attributes
    - `member_attrs` - Map or keyword list of member attributes

  ## Returns
    - Tuple of {user, member}

  ## Examples

      iex> {user, member} = linked_user_member_fixture()
      iex> user.member_id == member.id
      true

  """
  def linked_user_member_fixture(user_attrs \\ %{}, member_attrs \\ %{}) do
    member = member_fixture(member_attrs)

    user_attrs = Map.put(user_attrs, :member, %{id: member.id})
    user = user_fixture(user_attrs)

    {user, member}
  end

  @doc """
  Creates a role with a specific permission set.

  Uses system_actor for authorization to bypass permission checks in tests.

  ## Parameters
    - `permission_set_name` - The permission set name (e.g., "admin", "read_only", "normal_user", "own_data")

  ## Returns
    - Role struct

  ## Examples

      iex> role_fixture("admin")
      %Mv.Authorization.Role{permission_set_name: "admin", ...}

  """
  def role_fixture(permission_set_name) do
    system_actor = Mv.Helpers.SystemActor.get_system_actor()
    role_name = "Test Role #{permission_set_name} #{System.unique_integer([:positive])}"

    case Mv.Authorization.create_role(
           %{
             name: role_name,
             description: "Test role for #{permission_set_name}",
             permission_set_name: permission_set_name
           },
           actor: system_actor
         ) do
      {:ok, role} -> role
      {:error, error} -> raise "Failed to create role: #{inspect(error)}"
    end
  end

  @doc """
  Creates a user with a specific permission set (role).

  ## Parameters
    - `permission_set_name` - The permission set name (e.g., "admin", "read_only", "normal_user", "own_data")
    - `user_attrs` - Optional user attributes

  ## Returns
    - User struct with role preloaded

  ## Examples

      iex> admin_user = user_with_role_fixture("admin")
      iex> admin_user.role.permission_set_name
      "admin"

  """
  def user_with_role_fixture(permission_set_name \\ "admin", user_attrs \\ %{}) do
    system_actor = Mv.Helpers.SystemActor.get_system_actor()

    # Create role with permission set
    role = role_fixture(permission_set_name)

    # Create user
    {:ok, user} =
      user_attrs
      |> Enum.into(%{
        email: "user#{System.unique_integer([:positive])}@example.com"
      })
      |> Mv.Accounts.create_user(actor: system_actor)

    # Assign role to user
    {:ok, user} =
      user
      |> Ash.Changeset.for_update(:update, %{})
      |> Ash.Changeset.manage_relationship(:role, role, type: :append_and_remove)
      |> Ash.update(actor: system_actor)

    # Reload user with role preloaded (critical for authorization!)
    {:ok, user_with_role} = Ash.load(user, :role, domain: Mv.Accounts, actor: system_actor)
    user_with_role
  end

  @doc """
  Creates a user with password authentication and a specific role.

  This is useful for tests that need to use password-based registration
  but also require the user to have a role for authorization.

  ## Parameters
    - `email` - User email (defaults to unique generated email)
    - `password` - User password (defaults to "testpassword123")
    - `permission_set_name` - The permission set name (defaults to "own_data")

  ## Returns
    - User struct with role preloaded

  ## Examples

      iex> user = password_user_with_role_fixture()
      iex> user.role.permission_set_name
      "own_data"

  """
  def password_user_with_role_fixture(opts \\ %{}) do
    email = Map.get(opts, :email, "user#{System.unique_integer([:positive])}@example.com")
    password = Map.get(opts, :password, "testpassword123")
    permission_set_name = Map.get(opts, :permission_set_name, "own_data")

    # Create role with permission set
    role = role_fixture(permission_set_name)

    # Create user with password (without password_confirmation as it's optional)
    {:ok, user} =
      Mv.Accounts.User
      |> Ash.Changeset.for_create(:register_with_password, %{
        email: email,
        password: password
      })
      |> Ash.create()

    # Assign role to user
    {:ok, user} =
      user
      |> Ash.Changeset.for_update(:update, %{})
      |> Ash.Changeset.manage_relationship(:role, role, type: :append_and_remove)
      |> Ash.update()

    # Reload user with role preloaded
    {:ok, user_with_role} = Ash.load(user, :role, domain: Mv.Accounts)
    user_with_role
  end

  @doc """
  Creates a member with an actor (for use in tests with policies).

  ## Parameters
    - `attrs` - Map or keyword list of attributes to override defaults
    - `actor` - The actor (user) to use for authorization

  ## Returns
    - Member struct

  ## Examples

      iex> admin = user_with_role_fixture("admin")
      iex> member_fixture_with_actor(%{first_name: "Alice"}, admin)
      %Mv.Membership.Member{first_name: "Alice", ...}

  """
  def member_fixture_with_actor(attrs \\ %{}, actor) do
    attrs
    |> Enum.into(%{
      first_name: "Test",
      last_name: "Member",
      email: "test#{System.unique_integer([:positive])}@example.com"
    })
    |> Mv.Membership.create_member(actor: actor)
    |> case do
      {:ok, member} -> member
      {:error, error} -> raise "Failed to create member: #{inspect(error)}"
    end
  end

  @doc """
  Creates a group with default or custom attributes.

  Uses system_actor for authorization to bypass permission checks in tests.

  ## Parameters
    - `attrs` - Map or keyword list of attributes to override defaults

  ## Returns
    - Group struct

  ## Examples

      iex> group_fixture()
      %Mv.Membership.Group{name: "Test Group", slug: "test-group", ...}

      iex> group_fixture(%{name: "Board Members", description: "Board members group"})
      %Mv.Membership.Group{name: "Board Members", slug: "board-members", ...}

  """
  def group_fixture(attrs \\ %{}) do
    system_actor = Mv.Helpers.SystemActor.get_system_actor()

    attrs
    |> Enum.into(%{
      name: "Test Group #{System.unique_integer([:positive])}",
      description: "Test description"
    })
    |> Mv.Membership.create_group(actor: system_actor)
    |> case do
      {:ok, group} -> group
      {:error, error} -> raise "Failed to create group: #{inspect(error)}"
    end
  end
end
