defmodule Mv.Membership.MemberGroupsRelationshipTest do
  @moduledoc """
  Tests for Member resource extension with groups relationship.
  """
  use Mv.DataCase, async: true

  alias Mv.Membership

  require Ash.Query
  import Ash.Expr

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

  describe "Relationships" do
    test "member has many_to_many groups relationship (load with preloading)", %{actor: actor} do
      {:ok, member} = Membership.create_member(%{email: "test@test.com"}, actor: actor)
      {:ok, group1} = Membership.create_group(%{name: "Group One"}, actor: actor)
      {:ok, group2} = Membership.create_group(%{name: "Group Two"}, actor: actor)

      {:ok, _mg1} =
        Membership.create_member_group(%{member_id: member.id, group_id: group1.id},
          actor: actor
        )

      {:ok, _mg2} =
        Membership.create_member_group(%{member_id: member.id, group_id: group2.id},
          actor: actor
        )

      # Load member with groups
      {:ok, member_with_groups} =
        Ash.load(member, :groups, actor: actor, domain: Mv.Membership)

      assert length(member_with_groups.groups) == 2
      assert Enum.any?(member_with_groups.groups, &(&1.id == group1.id))
      assert Enum.any?(member_with_groups.groups, &(&1.id == group2.id))
    end

    test "load multiple members with groups preloaded (N+1 prevention)", %{actor: actor} do
      {:ok, member1} = Membership.create_member(%{email: "member1@test.com"}, actor: actor)
      {:ok, member2} = Membership.create_member(%{email: "member2@test.com"}, actor: actor)
      {:ok, group} = Membership.create_group(%{name: "Test Group"}, actor: actor)

      {:ok, _mg1} =
        Membership.create_member_group(%{member_id: member1.id, group_id: group.id},
          actor: actor
        )

      {:ok, _mg2} =
        Membership.create_member_group(%{member_id: member2.id, group_id: group.id},
          actor: actor
        )

      # Load all members with groups in single query
      {:ok, members} =
        Ash.read(Mv.Membership.Member, actor: actor, domain: Mv.Membership, load: [:groups])

      member1_loaded = Enum.find(members, &(&1.id == member1.id))
      member2_loaded = Enum.find(members, &(&1.id == member2.id))

      assert length(member1_loaded.groups) == 1
      assert length(member2_loaded.groups) == 1
      assert hd(member1_loaded.groups).id == group.id
      assert hd(member2_loaded.groups).id == group.id
    end
  end

  describe "Member-Group Association Operations" do
    test "add member to group via Ash API", %{actor: actor} do
      {:ok, member} = Membership.create_member(%{email: "test@test.com"}, actor: actor)
      {:ok, group} = Membership.create_group(%{name: "Test Group"}, actor: actor)

      assert {:ok, member_group} =
               Membership.create_member_group(%{member_id: member.id, group_id: group.id},
                 actor: actor
               )

      assert member_group.member_id == member.id
      assert member_group.group_id == group.id
    end

    test "remove member from group via Ash API", %{actor: actor} do
      {:ok, member} = Membership.create_member(%{email: "test@test.com"}, actor: actor)
      {:ok, group} = Membership.create_group(%{name: "Test Group"}, actor: actor)

      {:ok, member_group} =
        Membership.create_member_group(%{member_id: member.id, group_id: group.id},
          actor: actor
        )

      # Remove association
      :ok = Membership.destroy_member_group(member_group, actor: actor)

      # Verify association is removed
      {:ok, mgs} =
        Ash.read(
          Mv.Membership.MemberGroup
          |> Ash.Query.filter(expr(member_id == ^member.id and group_id == ^group.id)),
          actor: actor,
          domain: Mv.Membership
        )

      assert mgs == []
    end

    test "add member to multiple groups in single operation", %{actor: actor} do
      {:ok, member} = Membership.create_member(%{email: "test@test.com"}, actor: actor)
      {:ok, group1} = Membership.create_group(%{name: "Group One"}, actor: actor)
      {:ok, group2} = Membership.create_group(%{name: "Group Two"}, actor: actor)
      {:ok, group3} = Membership.create_group(%{name: "Group Three"}, actor: actor)

      # Add to all groups
      {:ok, _mg1} =
        Membership.create_member_group(%{member_id: member.id, group_id: group1.id},
          actor: actor
        )

      {:ok, _mg2} =
        Membership.create_member_group(%{member_id: member.id, group_id: group2.id},
          actor: actor
        )

      {:ok, _mg3} =
        Membership.create_member_group(%{member_id: member.id, group_id: group3.id},
          actor: actor
        )

      # Verify all associations exist
      {:ok, member_with_groups} =
        Ash.load(member, :groups, actor: actor, domain: Mv.Membership)

      assert length(member_with_groups.groups) == 3
    end
  end

  describe "Edge Cases" do
    test "adding member to same group twice fails (duplicate prevention)", %{actor: actor} do
      {:ok, member} = Membership.create_member(%{email: "test@test.com"}, actor: actor)
      {:ok, group} = Membership.create_group(%{name: "Test Group"}, actor: actor)

      {:ok, _mg1} =
        Membership.create_member_group(%{member_id: member.id, group_id: group.id},
          actor: actor
        )

      # Try to add again
      assert {:error, %Ash.Error.Invalid{}} =
               Membership.create_member_group(%{member_id: member.id, group_id: group.id},
                 actor: actor
               )
    end

    test "removing member from group they're not in (idempotent, no error)", %{actor: actor} do
      {:ok, member} = Membership.create_member(%{email: "test@test.com"}, actor: actor)
      {:ok, group} = Membership.create_group(%{name: "Test Group"}, actor: actor)

      # Verify no association exists
      {:ok, nil} =
        Ash.read_one(
          Mv.Membership.MemberGroup
          |> Ash.Query.filter(expr(member_id == ^member.id and group_id == ^group.id)),
          actor: actor,
          domain: Mv.Membership
        )

      # Test idempotency: Create association, delete it, then try to delete again
      # This verifies that destroy_member_group is idempotent
      {:ok, member_group} =
        Membership.create_member_group(%{member_id: member.id, group_id: group.id},
          actor: actor
        )

      # First deletion should succeed
      assert :ok = Membership.destroy_member_group(member_group, actor: actor)

      # Verify association is deleted
      {:ok, nil} =
        Ash.read_one(
          Mv.Membership.MemberGroup
          |> Ash.Query.filter(expr(id == ^member_group.id)),
          actor: actor,
          domain: Mv.Membership
        )

      # Try to destroy again - should be idempotent (either succeed or return not found error)
      # Note: This tests the idempotency of the destroy action
      result = Membership.destroy_member_group(member_group, actor: actor)

      # Should either succeed (idempotent) or return an error (not found)
      # Both behaviors are acceptable for idempotency
      assert result == :ok || match?({:error, _}, result)
    end
  end
end
