defmodule Mv.Membership.MemberEmailValidationTest do
  @moduledoc """
  Tests for Member email-change permission validation.

  When a member is linked to a user, only admins or the linked user may change
  that member's email. Unlinked members and non-email updates are unaffected.
  """
  use Mv.DataCase, async: false

  alias Mv.Accounts
  alias Mv.Helpers.SystemActor
  alias Mv.Membership

  setup do
    system_actor = SystemActor.get_system_actor()
    %{actor: system_actor}
  end

  defp create_linked_member_for_user(user, _actor) do
    admin = Mv.Fixtures.user_with_role_fixture("admin")

    {:ok, member} =
      Membership.create_member(
        %{
          first_name: "Linked",
          last_name: "Member",
          email: "linked#{System.unique_integer([:positive])}@example.com"
        },
        actor: admin
      )

    user
    |> Ash.Changeset.for_update(:update, %{})
    |> Ash.Changeset.force_change_attribute(:member_id, member.id)
    |> Ash.update(actor: admin, domain: Mv.Accounts, return_notifications?: false)

    member
  end

  defp create_unlinked_member(_actor) do
    admin = Mv.Fixtures.user_with_role_fixture("admin")

    {:ok, member} =
      Membership.create_member(
        %{
          first_name: "Unlinked",
          last_name: "Member",
          email: "unlinked#{System.unique_integer([:positive])}@example.com"
        },
        actor: admin
      )

    member
  end

  describe "unlinked member" do
    test "normal_user can update email of unlinked member", %{actor: actor} do
      normal_user = Mv.Fixtures.user_with_role_fixture("normal_user")
      unlinked_member = create_unlinked_member(actor)

      new_email = "new#{System.unique_integer([:positive])}@example.com"

      assert {:ok, updated} =
               Membership.update_member(unlinked_member, %{email: new_email}, actor: normal_user)

      assert updated.email == new_email
    end

    test "validation does not block when member has no linked user", %{actor: actor} do
      normal_user = Mv.Fixtures.user_with_role_fixture("normal_user")
      unlinked_member = create_unlinked_member(actor)

      new_email = "other#{System.unique_integer([:positive])}@example.com"

      assert {:ok, _} =
               Membership.update_member(unlinked_member, %{email: new_email}, actor: normal_user)
    end
  end

  describe "linked member – another user's member" do
    test "normal_user cannot update email of another user's linked member", %{actor: actor} do
      user_a = Mv.Fixtures.user_with_role_fixture("own_data")
      linked_member = create_linked_member_for_user(user_a, actor)

      normal_user_b = Mv.Fixtures.user_with_role_fixture("normal_user")
      new_email = "other#{System.unique_integer([:positive])}@example.com"

      assert {:error, %Ash.Error.Invalid{} = error} =
               Membership.update_member(linked_member, %{email: new_email}, actor: normal_user_b)

      assert Enum.any?(error.errors, &(&1.field == :email)),
             "expected an error for field :email, got: #{inspect(error.errors)}"
    end

    test "admin can update email of linked member", %{actor: actor} do
      user_a = Mv.Fixtures.user_with_role_fixture("own_data")
      linked_member = create_linked_member_for_user(user_a, actor)
      admin = Mv.Fixtures.user_with_role_fixture("admin")

      new_email = "admin_changed#{System.unique_integer([:positive])}@example.com"

      assert {:ok, updated} =
               Membership.update_member(linked_member, %{email: new_email}, actor: admin)

      assert updated.email == new_email
    end
  end

  describe "linked member – own member" do
    test "own_data user can update email of their own linked member", %{actor: actor} do
      own_data_user = Mv.Fixtures.user_with_role_fixture("own_data")
      linked_member = create_linked_member_for_user(own_data_user, actor)

      {:ok, own_data_user} =
        Ash.get(Accounts.User, own_data_user.id, domain: Mv.Accounts, load: [:role], actor: actor)

      {:ok, own_data_user} =
        Ash.load(own_data_user, :member, domain: Mv.Accounts, actor: actor)

      new_email = "own_updated#{System.unique_integer([:positive])}@example.com"

      assert {:ok, updated} =
               Membership.update_member(linked_member, %{email: new_email}, actor: own_data_user)

      assert updated.email == new_email
    end

    test "normal_user with linked member can update email of that same member", %{actor: actor} do
      normal_user = Mv.Fixtures.user_with_role_fixture("normal_user")
      linked_member = create_linked_member_for_user(normal_user, actor)

      {:ok, normal_user} =
        Ash.get(Accounts.User, normal_user.id, domain: Mv.Accounts, load: [:role], actor: actor)

      {:ok, normal_user} = Ash.load(normal_user, :member, domain: Mv.Accounts, actor: actor)

      new_email = "normal_own#{System.unique_integer([:positive])}@example.com"

      assert {:ok, updated} =
               Membership.update_member(linked_member, %{email: new_email}, actor: normal_user)

      assert updated.email == new_email
    end
  end

  describe "no-op / other fields" do
    test "updating only other attributes on linked member as normal_user does not trigger validation error",
         %{actor: actor} do
      user_a = Mv.Fixtures.user_with_role_fixture("own_data")
      linked_member = create_linked_member_for_user(user_a, actor)
      normal_user_b = Mv.Fixtures.user_with_role_fixture("normal_user")

      assert {:ok, updated} =
               Membership.update_member(linked_member, %{first_name: "UpdatedName"},
                 actor: normal_user_b
               )

      assert updated.first_name == "UpdatedName"
      assert updated.email == linked_member.email
    end

    test "updating email of linked member as admin succeeds", %{actor: actor} do
      user_a = Mv.Fixtures.user_with_role_fixture("own_data")
      linked_member = create_linked_member_for_user(user_a, actor)
      admin = Mv.Fixtures.user_with_role_fixture("admin")

      new_email = "admin_ok#{System.unique_integer([:positive])}@example.com"

      assert {:ok, updated} =
               Membership.update_member(linked_member, %{email: new_email}, actor: admin)

      assert updated.email == new_email
    end
  end

  describe "read_only" do
    test "read_only cannot update any member (policy rejects before validation)", %{actor: actor} do
      read_only_user = Mv.Fixtures.user_with_role_fixture("read_only")
      linked_member = create_linked_member_for_user(read_only_user, actor)

      {:ok, read_only_user} =
        Ash.get(Accounts.User, read_only_user.id,
          domain: Mv.Accounts,
          load: [:role],
          actor: actor
        )

      assert {:error, %Ash.Error.Forbidden{}} =
               Membership.update_member(linked_member, %{email: "changed@example.com"},
                 actor: read_only_user
               )
    end
  end
end
