defmodule Mv.Membership.MemberFuzzySearchLinkingTest do
  @moduledoc """
  Tests fuzzy search in Member.available_for_linking action.
  Verifies PostgreSQL trigram matching for member search.
  """

  use Mv.DataCase, async: false

  alias Mv.Accounts
  alias Mv.Membership

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

  describe "available_for_linking with fuzzy search" do
    test "finds member despite typo", %{actor: actor} do
      # Create member with specific name
      {:ok, member} =
        Membership.create_member(
          %{
            first_name: "Jonathan",
            last_name: "Smith",
            email: "jonathan@example.com"
          },
          actor: actor
        )

      # Search with typo
      query =
        Mv.Membership.Member
        |> Ash.Query.for_read(:available_for_linking, %{
          user_email: nil,
          search_query: "Jonatan"
        })

      {:ok, members} = Ash.read(query, domain: Mv.Membership, actor: actor)

      # Should find Jonathan despite typo
      assert length(members) == 1
      assert hd(members).id == member.id
    end

    test "finds member with partial match", %{actor: actor} do
      # Create member
      {:ok, member} =
        Membership.create_member(
          %{
            first_name: "Alexander",
            last_name: "Williams",
            email: "alex@example.com"
          },
          actor: actor
        )

      # Search with partial
      query =
        Mv.Membership.Member
        |> Ash.Query.for_read(:available_for_linking, %{
          user_email: nil,
          search_query: "Alex"
        })

      {:ok, members} = Ash.read(query, domain: Mv.Membership, actor: actor)

      # Should find Alexander
      assert length(members) == 1
      assert hd(members).id == member.id
    end

    test "email match overrides fuzzy search", %{actor: actor} do
      # Create two members
      {:ok, member1} =
        Membership.create_member(
          %{
            first_name: "John",
            last_name: "Doe",
            email: "john@example.com"
          },
          actor: actor
        )

      {:ok, _member2} =
        Membership.create_member(
          %{
            first_name: "Jane",
            last_name: "Smith",
            email: "jane@example.com"
          },
          actor: actor
        )

      # Search with user_email that matches member1, but search_query that would match member2
      query =
        Mv.Membership.Member
        |> Ash.Query.for_read(:available_for_linking, %{
          user_email: "john@example.com",
          search_query: "Jane"
        })

      {:ok, members} = Ash.read(query, domain: Mv.Membership, actor: actor)

      # Apply email filter
      filtered_members = Mv.Membership.Member.filter_by_email_match(members, "john@example.com")

      # Should only return member1 (email match takes precedence)
      assert length(filtered_members) == 1
      assert hd(filtered_members).id == member1.id
    end

    test "limits to 10 results", %{actor: actor} do
      # Create 15 members with similar names
      for i <- 1..15 do
        Membership.create_member(
          %{
            first_name: "Test#{i}",
            last_name: "Member",
            email: "test#{i}@example.com"
          },
          actor: actor
        )
      end

      # Search for "Test"
      query =
        Mv.Membership.Member
        |> Ash.Query.for_read(:available_for_linking, %{
          user_email: nil,
          search_query: "Test"
        })

      {:ok, members} = Ash.read(query, domain: Mv.Membership, actor: actor)

      # Should return max 10 members
      assert length(members) == 10
    end

    test "excludes linked members", %{actor: actor} do
      # Create member and link to user
      {:ok, member1} =
        Membership.create_member(
          %{
            first_name: "Linked",
            last_name: "Member",
            email: "linked@example.com"
          },
          actor: actor
        )

      {:ok, _user} =
        Accounts.create_user(
          %{
            email: "user@example.com",
            member: %{id: member1.id}
          },
          actor: actor
        )

      # Create unlinked member
      {:ok, member2} =
        Membership.create_member(
          %{
            first_name: "Unlinked",
            last_name: "Member",
            email: "unlinked@example.com"
          },
          actor: actor
        )

      # Search for "Member"
      query =
        Mv.Membership.Member
        |> Ash.Query.for_read(:available_for_linking, %{
          user_email: nil,
          search_query: "Member"
        })

      {:ok, members} = Ash.read(query, domain: Mv.Membership, actor: actor)

      # Should only return unlinked member
      member_ids = Enum.map(members, & &1.id)
      refute member1.id in member_ids
      assert member2.id in member_ids
    end
  end
end
