defmodule Mv.Membership.MemberAvailableForLinkingTest do
  @moduledoc """
  Tests for the Member.available_for_linking action.

  This action returns members that can be linked to a user account:
  - Only members without existing user links (user_id == nil)
  - Limited to 10 results
  - Special email-match logic: if user_email matches member email, only return that member
  - Optional search query filtering by name and email
  """
  use Mv.DataCase, async: false
  alias Mv.Membership

  describe "available_for_linking/2" do
    setup do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()

      # Create 5 unlinked members with distinct names
      {:ok, member1} =
        Membership.create_member(
          %{
            first_name: "Alice",
            last_name: "Anderson",
            email: "alice@example.com"
          },
          actor: system_actor
        )

      {:ok, member2} =
        Membership.create_member(
          %{
            first_name: "Bob",
            last_name: "Williams",
            email: "bob@example.com"
          },
          actor: system_actor
        )

      {:ok, member3} =
        Membership.create_member(
          %{
            first_name: "Charlie",
            last_name: "Davis",
            email: "charlie@example.com"
          },
          actor: system_actor
        )

      {:ok, member4} =
        Membership.create_member(
          %{
            first_name: "Diana",
            last_name: "Martinez",
            email: "diana@example.com"
          },
          actor: system_actor
        )

      {:ok, member5} =
        Membership.create_member(
          %{
            first_name: "Emma",
            last_name: "Taylor",
            email: "emma@example.com"
          },
          actor: system_actor
        )

      unlinked_members = [member1, member2, member3, member4, member5]

      # Create 2 linked members (with users)
      {:ok, user1} = Mv.Accounts.create_user(%{email: "user1@example.com"}, actor: system_actor)

      {:ok, linked_member1} =
        Membership.create_member(
          %{
            first_name: "Linked",
            last_name: "Member1",
            email: "linked1@example.com",
            user: %{id: user1.id}
          },
          actor: system_actor
        )

      {:ok, user2} = Mv.Accounts.create_user(%{email: "user2@example.com"}, actor: system_actor)

      {:ok, linked_member2} =
        Membership.create_member(
          %{
            first_name: "Linked",
            last_name: "Member2",
            email: "linked2@example.com",
            user: %{id: user2.id}
          },
          actor: system_actor
        )

      %{
        unlinked_members: unlinked_members,
        linked_members: [linked_member1, linked_member2]
      }
    end

    test "returns only unlinked members and limits to 10", %{
      unlinked_members: unlinked_members,
      linked_members: _linked_members
    } do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()

      # Call the action without any arguments
      members =
        Mv.Membership.Member
        |> Ash.Query.for_read(:available_for_linking, %{})
        |> Ash.read!(actor: system_actor)

      # Should return only the 5 unlinked members, not the 2 linked ones
      assert length(members) == 5

      returned_ids = Enum.map(members, & &1.id) |> MapSet.new()
      expected_ids = Enum.map(unlinked_members, & &1.id) |> MapSet.new()

      assert MapSet.equal?(returned_ids, expected_ids)

      # Verify none of the returned members have a user_id
      Enum.each(members, fn member ->
        member_with_user =
          Ash.get!(Mv.Membership.Member, member.id, actor: system_actor, load: [:user])

        assert is_nil(member_with_user.user)
      end)
    end

    @tag :slow
    test "limits results to 10 members even when more exist" do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()

      # Create 15 additional unlinked members (total 20 unlinked)
      for i <- 6..20 do
        Membership.create_member(
          %{
            first_name: "Extra#{i}",
            last_name: "Member#{i}",
            email: "extra#{i}@example.com"
          },
          actor: system_actor
        )
      end

      members =
        Mv.Membership.Member
        |> Ash.Query.for_read(:available_for_linking, %{})
        |> Ash.read!(actor: system_actor)

      # Should be limited to 10
      assert length(members) == 10
    end

    test "email match: returns only member with matching email when exists", %{
      unlinked_members: unlinked_members
    } do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()

      # Get one of the unlinked members' email
      target_member = List.first(unlinked_members)
      user_email = target_member.email

      raw_members =
        Mv.Membership.Member
        |> Ash.Query.for_read(:available_for_linking, %{user_email: user_email})
        |> Ash.read!(actor: system_actor)

      # Apply email match filtering (sorted results come from query)
      # When user_email matches, only that member should be returned
      members = Mv.Membership.Member.filter_by_email_match(raw_members, user_email)

      # Should return only the member with matching email
      assert length(members) == 1
      assert List.first(members).id == target_member.id
      assert List.first(members).email == user_email
    end

    test "email match: returns all unlinked members when no email match" do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()

      # Use an email that doesn't match any member
      non_matching_email = "nonexistent@example.com"

      raw_members =
        Mv.Membership.Member
        |> Ash.Query.for_read(:available_for_linking, %{user_email: non_matching_email})
        |> Ash.read!(actor: system_actor)

      # Apply email match filtering
      members = Mv.Membership.Member.filter_by_email_match(raw_members, non_matching_email)

      # Should return all 5 unlinked members since no match
      assert length(members) == 5
    end

    test "search query: filters by first_name, last_name, and email", %{
      unlinked_members: _unlinked_members
    } do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()

      # Search by first name
      members =
        Mv.Membership.Member
        |> Ash.Query.for_read(:available_for_linking, %{search_query: "Alice"})
        |> Ash.read!(actor: system_actor)

      assert length(members) == 1
      assert List.first(members).first_name == "Alice"

      # Search by last name
      members =
        Mv.Membership.Member
        |> Ash.Query.for_read(:available_for_linking, %{search_query: "Williams"})
        |> Ash.read!(actor: system_actor)

      assert length(members) == 1
      assert List.first(members).last_name == "Williams"

      # Search by email
      members =
        Mv.Membership.Member
        |> Ash.Query.for_read(:available_for_linking, %{search_query: "charlie@"})
        |> Ash.read!(actor: system_actor)

      assert length(members) == 1
      assert List.first(members).email == "charlie@example.com"

      # Search returns empty when no matches
      members =
        Mv.Membership.Member
        |> Ash.Query.for_read(:available_for_linking, %{search_query: "NonExistent"})
        |> Ash.read!(actor: system_actor)

      assert Enum.empty?(members)
    end

    test "user_email takes precedence over search_query", %{unlinked_members: unlinked_members} do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      target_member = List.first(unlinked_members)

      # Pass both email match and search query that would match different members
      raw_members =
        Mv.Membership.Member
        |> Ash.Query.for_read(:available_for_linking, %{
          user_email: target_member.email,
          search_query: "Bob"
        })
        |> Ash.read!(actor: system_actor)

      # Apply email-match filter (as LiveView does)
      members = Mv.Membership.Member.filter_by_email_match(raw_members, target_member.email)

      # Email takes precedence: should match target_member by email, ignoring search_query
      assert length(members) == 1
      assert List.first(members).id == target_member.id
    end
  end
end
