defmodule MvWeb.MemberLive.IndexTest do
  use MvWeb.ConnCase, async: false
  import Phoenix.LiveViewTest
  require Ash.Query

  alias Mv.MembershipFees.MembershipFeeType
  alias Mv.MembershipFees.MembershipFeeCycle

  # Helper to create a membership fee type (shared across all tests)
  defp create_fee_type(attrs, actor) do
    default_attrs = %{
      name: "Test Fee Type #{System.unique_integer([:positive])}",
      amount: Decimal.new("50.00"),
      interval: :yearly
    }

    attrs = Map.merge(default_attrs, attrs)

    MembershipFeeType
    |> Ash.Changeset.for_create(:create, attrs)
    |> Ash.create!(actor: actor)
  end

  # Helper to create a cycle (shared across all tests)
  defp create_cycle(member, fee_type, attrs, actor) do
    # Delete any auto-generated cycles first to avoid conflicts
    existing_cycles =
      MembershipFeeCycle
      |> Ash.Query.filter(member_id == ^member.id)
      |> Ash.read!(actor: actor)

    Enum.each(existing_cycles, fn cycle -> Ash.destroy!(cycle, actor: actor) end)

    default_attrs = %{
      cycle_start: ~D[2023-01-01],
      amount: Decimal.new("50.00"),
      member_id: member.id,
      membership_fee_type_id: fee_type.id,
      status: :unpaid
    }

    attrs = Map.merge(default_attrs, attrs)

    MembershipFeeCycle
    |> Ash.Changeset.for_create(:create, attrs)
    |> Ash.create!(actor: actor)
  end

  describe "translations" do
    @describetag :ui

    test "shows translated title and button text by locale", %{conn: conn} do
      locales = [
        {"de", "Mitglieder", "Speichern",
         fn c -> Plug.Test.init_test_session(c, locale: "de") end},
        {"en", "Members", "Save",
         fn c ->
           Gettext.put_locale(MvWeb.Gettext, "en")
           c
         end}
      ]

      for {_locale, expected_title, expected_button, set_locale} <- locales do
        base = conn_with_oidc_user(conn) |> set_locale.()

        {:ok, _view, index_html} = live(base, "/members")
        assert index_html =~ expected_title

        base_form = conn_with_oidc_user(conn) |> set_locale.()
        {:ok, _view, form_html} = live(base_form, "/members/new")
        assert form_html =~ expected_button
      end
    end

    test "shows translated flash message after creating a member in German", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      conn = Plug.Test.init_test_session(conn, locale: "de")
      {:ok, form_view, _html} = live(conn, "/members/new")

      form_data = %{
        "member[first_name]" => "Max",
        "member[last_name]" => "Mustermann",
        "member[email]" => "max@example.com"
      }

      # Submit form and follow the redirect to get the flash message
      {:ok, index_view, _html} =
        form_view
        |> form("#member-form", form_data)
        |> render_submit()
        |> follow_redirect(conn, "/members")

      assert has_element?(index_view, "#flash-group", "Mitglied wurde erfolgreich erstellt")
    end

    test "shows translated flash message after creating a member in English", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      {:ok, form_view, _html} = live(conn, "/members/new")

      form_data = %{
        "member[first_name]" => "Max",
        "member[last_name]" => "Mustermann",
        "member[email]" => "max@example.com"
      }

      # Submit form and follow the redirect to get the flash message
      {:ok, index_view, _html} =
        form_view
        |> form("#member-form", form_data)
        |> render_submit()
        |> follow_redirect(conn, "/members")

      assert has_element?(index_view, "#flash-group", "Member created successfully")
    end
  end

  describe "sorting integration" do
    @describetag :ui
    test "clicking a column header toggles sort order and updates the URL", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      {:ok, view, _html} = live(conn, "/members")

      # The component data test ids are built with the name of the field
      # First click – should sort ASC
      view
      |> element("[data-testid='email']")
      |> render_click()

      # The LiveView pushes a patch with the new query params
      assert_patch(view, "/members?query=&sort_field=email&sort_order=asc")

      # Second click – toggles to DESC
      view
      |> element("[data-testid='email']")
      |> render_click()

      assert_patch(view, "/members?query=&sort_field=email&sort_order=desc")
    end

    test "clicking different column header resets order to ascending", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      {:ok, view, _html} = live(conn, "/members?sort_field=email&sort_order=desc")

      # Click on a different column
      view
      |> element("[data-testid='first_name']")
      |> render_click()

      assert_patch(view, "/members?query=&sort_field=first_name&sort_order=asc")
    end

    test "all sortable columns work correctly", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      {:ok, view, _html} = live(conn, "/members")

      # default ascending sorting with first name
      assert has_element?(view, "[data-testid='first_name'][aria-label='ascending']")

      sortable_fields = [
        :email,
        :street,
        :house_number,
        :postal_code,
        :city,
        :join_date
      ]

      for field <- sortable_fields do
        view
        |> element("[data-testid='#{field}']")
        |> render_click()

        assert_patch(view, "/members?query=&sort_field=#{field}&sort_order=asc")
      end
    end

    test "sorting works with search query", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      {:ok, view, _html} = live(conn, "/members?query=test")

      view
      |> element("[data-testid='email']")
      |> render_click()

      assert_patch(view, "/members?query=test&sort_field=email&sort_order=asc")
    end

    test "sorting maintains search query when toggling order", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      {:ok, view, _html} = live(conn, "/members?query=test&sort_field=email&sort_order=asc")

      view
      |> element("[data-testid='email']")
      |> render_click()

      assert_patch(view, "/members?query=test&sort_field=email&sort_order=desc")
    end
  end

  describe "URL param handling" do
    @describetag :ui
    test "handle_params reads sort query and applies it", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      {:ok, view, _html} = live(conn, "/members?query=&sort_field=email&sort_order=desc")

      # Check that the sort state is correctly applied
      assert has_element?(view, "[data-testid='email'][aria-label='descending']")
    end

    test "handle_params handles invalid sort field gracefully", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      {:ok, view, _html} = live(conn, "/members?query=&sort_field=invalid_field&sort_order=asc")

      # Should not crash and should show default first name order
      assert has_element?(view, "[data-testid='first_name'][aria-label='ascending']")
    end

    test "handle_params preserves search query with sort params", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      {:ok, view, _html} = live(conn, "/members?query=test&sort_field=email&sort_order=desc")

      # Both search and sort should be preserved
      assert has_element?(view, "[data-testid='email'][aria-label='descending']")
    end
  end

  describe "search and sort integration" do
    @describetag :ui
    test "search maintains sort state", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      {:ok, view, _html} = live(conn, "/members?query=&sort_field=email&sort_order=desc")

      # Perform search
      view
      |> element("[data-testid='search-input']")
      |> render_change(%{value: "test"})

      # Sort state should be maintained
      assert has_element?(view, "[data-testid='email'][aria-label='descending']")
    end

    test "sort maintains search state", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      {:ok, view, _html} = live(conn, "/members?query=test&sort_field=email&sort_order=asc")

      # Perform sort
      view
      |> element("[data-testid='email']")
      |> render_click()

      # Search state should be maintained
      assert_patch(view, "/members?query=test&sort_field=email&sort_order=desc")
    end
  end

  @tag :ui
  test "handle_info(:search_changed) updates assigns with search results", %{conn: conn} do
    conn = conn_with_oidc_user(conn)
    {:ok, view, _html} = live(conn, "/members")

    send(view.pid, {:search_changed, "Friedrich"})

    state = :sys.get_state(view.pid)

    assert state.socket.assigns.query == "Friedrich"
    assert is_list(state.socket.assigns.members)
  end

  test "can delete a member without error", %{conn: conn} do
    system_actor = Mv.Helpers.SystemActor.get_system_actor()

    # Create a test member first
    {:ok, member} =
      Mv.Membership.create_member(
        %{
          first_name: "Test",
          last_name: "User",
          email: "test@example.com"
        },
        actor: system_actor
      )

    conn = conn_with_oidc_user(conn)
    {:ok, index_view, _html} = live(conn, "/members")

    # Verify the member is displayed
    assert has_element?(index_view, "#members", "Test User")

    # Click the delete link for this member
    index_view
    |> element("a", "Delete")
    |> render_click()

    # Verify the member is no longer displayed
    refute has_element?(index_view, "#members", "Test User")

    # Verify the member was actually deleted from the database
    assert not (Mv.Membership.Member |> Ash.Query.filter(id == ^member.id) |> Ash.exists?())
  end

  describe "copy_emails feature" do
    setup do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()

      # Create test members
      {:ok, member1} =
        Mv.Membership.create_member(
          %{
            first_name: "Max",
            last_name: "Mustermann",
            email: "max@example.com"
          },
          actor: system_actor
        )

      {:ok, member2} =
        Mv.Membership.create_member(
          %{
            first_name: "Erika",
            last_name: "Musterfrau",
            email: "erika@example.com"
          },
          actor: system_actor
        )

      {:ok, member3} =
        Mv.Membership.create_member(
          %{
            first_name: "Hans",
            last_name: "Müller-Lüdenscheidt",
            email: "hans@example.com"
          },
          actor: system_actor
        )

      %{member1: member1, member2: member2, member3: member3}
    end

    test "copy_emails event formats selected members correctly", %{
      conn: conn,
      member1: member1,
      member2: member2
    } do
      conn = conn_with_oidc_user(conn)
      {:ok, view, _html} = live(conn, "/members")

      # Select two members by sending the select_member event directly
      render_click(view, "select_member", %{"id" => member1.id})
      render_click(view, "select_member", %{"id" => member2.id})

      # Trigger copy_emails event
      view |> element("#copy-emails-btn") |> render_click()

      # Verify flash message shows correct count
      assert render(view) =~ "2"
    end

    test "copy_emails event with no selection shows error flash", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      {:ok, view, _html} = live(conn, "/members")

      # Trigger copy_emails event directly (button not visible when no selection)
      # This tests the edge case where event is triggered without selection
      result = render_hook(view, "copy_emails", %{})

      # Should show error flash
      assert result =~ "No members selected" or result =~ "Keine Mitglieder"
    end

    test "copy_emails event with all members selected formats all emails", %{
      conn: conn
    } do
      conn = conn_with_oidc_user(conn)
      {:ok, view, _html} = live(conn, "/members")

      # Select all members via select_all
      view |> element("[phx-click='select_all']") |> render_click()

      # Trigger copy_emails event
      view |> element("#copy-emails-btn") |> render_click()

      # Verify flash message shows correct count (3 members)
      assert render(view) =~ "3"
    end

    test "copy_emails handles members with special characters in names", %{
      conn: conn,
      member3: member3
    } do
      conn = conn_with_oidc_user(conn)
      {:ok, view, _html} = live(conn, "/members")

      # Select member with umlauts by sending the select_member event directly
      render_click(view, "select_member", %{"id" => member3.id})

      # Trigger copy_emails event - should not crash
      view |> element("#copy-emails-btn") |> render_click()

      # Verify flash message shows success
      assert render(view) =~ "1"
    end

    test "copy_emails handles case where selected member is deleted before copy", %{
      conn: conn,
      member1: member1
    } do
      conn = conn_with_oidc_user(conn)
      {:ok, view, _html} = live(conn, "/members")

      # Select a member by sending the select_member event directly
      render_click(view, "select_member", %{"id" => member1.id})

      # Delete the member from the database
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      Ash.destroy!(member1, actor: system_actor)

      # Trigger copy_emails event directly - selection still contains the deleted ID
      # but the member is no longer in @members list after reload
      result = render_hook(view, "copy_emails", %{})

      # Should show error since no visible members match selection
      assert result =~ "No email" or result =~ "Keine E-Mail" or result =~ "0"
    end

    test "copy_emails formats emails as RFC 5322 compliant comma-separated list", %{
      conn: conn,
      member1: member1,
      member2: member2
    } do
      conn = conn_with_oidc_user(conn)
      {:ok, view, _html} = live(conn, "/members")

      # Select two members by sending the select_member event directly
      render_click(view, "select_member", %{"id" => member1.id})
      render_click(view, "select_member", %{"id" => member2.id})

      # Get the socket state to verify the formatted email string
      state = :sys.get_state(view.pid)
      selected_members = state.socket.assigns.selected_members

      # Verify MapSet is used
      assert %MapSet{} = selected_members
      assert MapSet.size(selected_members) == 2
    end

    test "email format is 'First Last <email>' with comma separator", %{
      conn: conn,
      member1: _member1
    } do
      # Test the format_member_email function indirectly
      # by checking the push_event payload structure
      conn = conn_with_oidc_user(conn)

      # Create a member with known data
      system_actor = Mv.Helpers.SystemActor.get_system_actor()

      {:ok, test_member} =
        Mv.Membership.create_member(
          %{
            first_name: "Test",
            last_name: "Format",
            email: "test.format@example.com"
          },
          actor: system_actor
        )

      {:ok, view, _html} = live(conn, "/members")

      # Select the test member by sending the select_member event directly
      render_click(view, "select_member", %{"id" => test_member.id})

      # The format should be "Test Format <test.format@example.com>"
      # We verify this by checking the flash shows 1 email was copied
      view |> element("#copy-emails-btn") |> render_click()
      assert render(view) =~ "1"
    end

    test "copy button is disabled when no members selected", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      {:ok, view, _html} = live(conn, "/members")

      # Copy button should be disabled (button element)
      assert has_element?(view, "#copy-emails-btn[disabled]")
      # Open email button should be disabled (link with tabindex and aria-disabled)
      assert has_element?(view, "#open-email-btn[tabindex='-1'][aria-disabled='true']")
    end

    test "copy button is enabled after selection", %{
      conn: conn,
      member1: member1
    } do
      conn = conn_with_oidc_user(conn)
      {:ok, view, _html} = live(conn, "/members")

      # Select a member by sending the select_member event directly
      render_click(view, "select_member", %{"id" => member1.id})

      # Copy button should now be enabled (no disabled attribute)
      refute has_element?(view, "#copy-emails-btn[disabled]")
      # Open email button should now be enabled (no tabindex=-1 or aria-disabled)
      refute has_element?(view, "#open-email-btn[tabindex='-1']")
      refute has_element?(view, "#open-email-btn[aria-disabled='true']")
      # Counter should show correct count
      assert render(view) =~ "1"
    end

    test "copy button click triggers event and shows flash", %{
      conn: conn,
      member1: member1
    } do
      conn = conn_with_oidc_user(conn)
      {:ok, view, _html} = live(conn, "/members")

      # Select a member by sending the select_member event directly
      render_click(view, "select_member", %{"id" => member1.id})

      # Click copy button
      view |> element("#copy-emails-btn") |> render_click()

      # Flash message should appear
      assert has_element?(view, "#flash-group")
    end
  end

  describe "export dropdown" do
    setup do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()

      {:ok, m1} =
        Mv.Membership.create_member(
          %{first_name: "Export", last_name: "One", email: "export1@example.com"},
          actor: system_actor
        )

      %{member1: m1}
    end

    test "export dropdown button is rendered when no selection and shows (all)", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      {:ok, _view, html} = live(conn, "/members")

      # Dropdown button should be present
      assert html =~ ~s(data-testid="export-dropdown")
      assert html =~ ~s(data-testid="export-dropdown-button")
      assert html =~ "Export"
      # Button text shows "all" when 0 selected (locale-dependent)
      assert html =~ "all" or html =~ "All"
    end

    test "after select_member event export dropdown shows (1)", %{conn: conn, member1: member1} do
      conn = conn_with_oidc_user(conn)
      {:ok, view, _html} = live(conn, "/members")

      render_click(view, "select_member", %{"id" => member1.id})

      html = render(view)
      assert html =~ "Export"
      assert html =~ "(1)"
    end

    test "dropdown opens and closes on click", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      {:ok, view, _html} = live(conn, "/members")

      # Initially closed
      refute has_element?(view, ~s([data-testid="export-dropdown-menu"]))

      # Click to open
      view
      |> element(~s([data-testid="export-dropdown-button"]))
      |> render_click()

      # Menu should be visible
      assert has_element?(view, ~s([data-testid="export-dropdown-menu"]))

      # Click to close
      view
      |> element(~s([data-testid="export-dropdown-button"]))
      |> render_click()

      # Menu should be hidden
      refute has_element?(view, ~s([data-testid="export-dropdown-menu"]))
    end

    test "dropdown has click-away and ESC handlers", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      {:ok, view, _html} = live(conn, "/members")

      # Open dropdown
      view
      |> element(~s([data-testid="export-dropdown-button"]))
      |> render_click()

      html = render(view)
      assert has_element?(view, ~s([data-testid="export-dropdown-menu"]))

      # Check that click-away handler is present
      assert html =~ ~s(phx-click-away="close_dropdown")
      # Check that ESC handler is present
      assert html =~ ~s(phx-window-keydown="close_dropdown")
      assert html =~ ~s(phx-key="Escape")
    end

    test "dropdown menu contains CSV and PDF export links with correct payload", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      {:ok, view, _html} = live(conn, "/members")

      # Open dropdown
      view
      |> element(~s([data-testid="export-dropdown-button"]))
      |> render_click()

      html = render(view)

      # Check CSV link
      assert html =~ ~s(data-testid="export-csv-link")
      assert html =~ "/members/export.csv"
      assert html =~ ~s(name="payload")
      assert html =~ ~s(type="hidden")
      assert html =~ ~s(name="_csrf_token")

      # Check PDF link
      assert html =~ ~s(data-testid="export-pdf-link")
      assert html =~ "/members/export.pdf"
      assert html =~ ~s(name="payload")
      assert html =~ ~s(type="hidden")
      assert html =~ ~s(name="_csrf_token")

      # Both forms should have the same payload
      csv_form_payload = extract_payload_from_form(html, "/members/export.csv")
      pdf_form_payload = extract_payload_from_form(html, "/members/export.pdf")

      assert csv_form_payload == pdf_form_payload
      assert csv_form_payload != nil
    end

    test "dropdown has correct ARIA attributes", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      {:ok, view, _html} = live(conn, "/members")

      html = render(view)

      # Button should have aria-haspopup="menu"
      assert html =~ ~s(aria-haspopup="menu")
      # Button should have aria-expanded="false" when closed
      assert html =~ ~s(aria-expanded="false")
      # Button should have aria-controls pointing to menu
      assert html =~ ~s(aria-controls="export-dropdown-menu")

      # Open dropdown
      view
      |> element(~s([data-testid="export-dropdown-button"]))
      |> render_click()

      html = render(view)
      # Button should have aria-expanded="true" when open
      assert html =~ ~s(aria-expanded="true")
      # Menu should have role="menu"
      assert html =~ ~s(role="menu")
    end

    # Helper to extract payload value from form HTML
    defp extract_payload_from_form(html, action_path) do
      case Regex.run(
             ~r/<form[^>]*action="#{Regex.escape(action_path)}"[^>]*>.*?<input[^>]*name="payload"[^>]*value="([^"]+)"/s,
             html
           ) do
        [_, payload] -> payload
        _ -> nil
      end
    end
  end

  describe "cycle status filter" do
    # Helper to create a member (only used in this describe block)
    defp create_member(attrs, actor) do
      default_attrs = %{
        first_name: "Test",
        last_name: "Member",
        email: "test.member.#{System.unique_integer([:positive])}@example.com"
      }

      attrs = Map.merge(default_attrs, attrs)
      {:ok, member} = Mv.Membership.create_member(attrs, actor: actor)
      member
    end

    test "filter shows only members with paid status in last cycle", %{conn: conn} do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      conn = conn_with_oidc_user(conn)
      fee_type = create_fee_type(%{interval: :yearly}, system_actor)
      today = Date.utc_today()
      last_year_start = Date.new!(today.year - 1, 1, 1)

      # Member with paid last cycle
      paid_member =
        create_member(
          %{
            first_name: "PaidLast",
            membership_fee_type_id: fee_type.id
          },
          system_actor
        )

      create_cycle(
        paid_member,
        fee_type,
        %{cycle_start: last_year_start, status: :paid},
        system_actor
      )

      # Member with unpaid last cycle
      unpaid_member =
        create_member(
          %{
            first_name: "UnpaidLast",
            membership_fee_type_id: fee_type.id
          },
          system_actor
        )

      create_cycle(
        unpaid_member,
        fee_type,
        %{cycle_start: last_year_start, status: :unpaid},
        system_actor
      )

      {:ok, _view, html} = live(conn, "/members?cycle_status_filter=paid")

      assert html =~ "PaidLast"
      refute html =~ "UnpaidLast"
    end

    test "filter shows only members with unpaid status in last cycle", %{conn: conn} do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      conn = conn_with_oidc_user(conn)
      fee_type = create_fee_type(%{interval: :yearly}, system_actor)
      today = Date.utc_today()
      last_year_start = Date.new!(today.year - 1, 1, 1)

      # Member with paid last cycle
      paid_member =
        create_member(
          %{
            first_name: "PaidLast",
            membership_fee_type_id: fee_type.id
          },
          system_actor
        )

      create_cycle(
        paid_member,
        fee_type,
        %{cycle_start: last_year_start, status: :paid},
        system_actor
      )

      # Member with unpaid last cycle
      unpaid_member =
        create_member(
          %{
            first_name: "UnpaidLast",
            membership_fee_type_id: fee_type.id
          },
          system_actor
        )

      create_cycle(
        unpaid_member,
        fee_type,
        %{cycle_start: last_year_start, status: :unpaid},
        system_actor
      )

      {:ok, _view, html} = live(conn, "/members?cycle_status_filter=unpaid")

      refute html =~ "PaidLast"
      assert html =~ "UnpaidLast"
    end

    test "filter shows only members with paid status in current cycle", %{conn: conn} do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      conn = conn_with_oidc_user(conn)
      fee_type = create_fee_type(%{interval: :yearly}, system_actor)
      today = Date.utc_today()
      current_year_start = Date.new!(today.year, 1, 1)

      # Member with paid current cycle
      paid_member =
        create_member(
          %{
            first_name: "PaidCurrent",
            membership_fee_type_id: fee_type.id
          },
          system_actor
        )

      create_cycle(
        paid_member,
        fee_type,
        %{cycle_start: current_year_start, status: :paid},
        system_actor
      )

      # Member with unpaid current cycle
      unpaid_member =
        create_member(
          %{
            first_name: "UnpaidCurrent",
            membership_fee_type_id: fee_type.id
          },
          system_actor
        )

      create_cycle(
        unpaid_member,
        fee_type,
        %{cycle_start: current_year_start, status: :unpaid},
        system_actor
      )

      {:ok, _view, html} = live(conn, "/members?cycle_status_filter=paid&show_current_cycle=true")

      assert html =~ "PaidCurrent"
      refute html =~ "UnpaidCurrent"
    end

    test "filter shows only members with unpaid status in current cycle", %{conn: conn} do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      conn = conn_with_oidc_user(conn)
      fee_type = create_fee_type(%{interval: :yearly}, system_actor)
      today = Date.utc_today()
      current_year_start = Date.new!(today.year, 1, 1)

      # Member with paid current cycle
      paid_member =
        create_member(
          %{
            first_name: "PaidCurrent",
            membership_fee_type_id: fee_type.id
          },
          system_actor
        )

      create_cycle(
        paid_member,
        fee_type,
        %{cycle_start: current_year_start, status: :paid},
        system_actor
      )

      # Member with unpaid current cycle
      unpaid_member =
        create_member(
          %{
            first_name: "UnpaidCurrent",
            membership_fee_type_id: fee_type.id
          },
          system_actor
        )

      create_cycle(
        unpaid_member,
        fee_type,
        %{cycle_start: current_year_start, status: :unpaid},
        system_actor
      )

      {:ok, _view, html} =
        live(conn, "/members?cycle_status_filter=unpaid&show_current_cycle=true")

      refute html =~ "PaidCurrent"
      assert html =~ "UnpaidCurrent"
    end

    test "toggle cycle view updates URL and preserves filter", %{conn: conn} do
      conn = conn_with_oidc_user(conn)

      # Start with last cycle view and paid filter
      {:ok, view, _html} = live(conn, "/members?cycle_status_filter=paid")

      # Toggle to current cycle - this should update URL and preserve filter
      # Use the button in the toolbar
      view
      |> element("button[phx-click='toggle_cycle_view']")
      |> render_click()

      # Wait for patch to complete
      path = assert_patch(view)

      # URL should contain both filter and show_current_cycle
      assert path =~ "cycle_status_filter=paid"
      assert path =~ "show_current_cycle=true"
    end
  end

  describe "boolean custom field filters" do
    alias Mv.Membership.CustomField

    # Helper to create a boolean custom field (uses system actor for authorization)
    defp create_boolean_custom_field(attrs \\ %{}) do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()

      default_attrs = %{
        name: "test_boolean_#{System.unique_integer([:positive])}",
        value_type: :boolean
      }

      attrs = Map.merge(default_attrs, attrs)

      CustomField
      |> Ash.Changeset.for_create(:create, attrs)
      |> Ash.create!(actor: system_actor)
    end

    # Helper to create a non-boolean custom field (uses system actor for authorization)
    defp create_string_custom_field(attrs \\ %{}) do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()

      default_attrs = %{
        name: "test_string_#{System.unique_integer([:positive])}",
        value_type: :string
      }

      attrs = Map.merge(default_attrs, attrs)

      CustomField
      |> Ash.Changeset.for_create(:create, attrs)
      |> Ash.create!(actor: system_actor)
    end

    @tag :ui
    test "mount initializes boolean_custom_field_filters as empty map", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      {:ok, view, _html} = live(conn, "/members")

      state = :sys.get_state(view.pid)
      assert state.socket.assigns.boolean_custom_field_filters == %{}
    end

    @tag :ui
    test "mount initializes boolean_custom_fields as empty list when no boolean fields exist", %{
      conn: conn
    } do
      conn = conn_with_oidc_user(conn)
      {:ok, view, _html} = live(conn, "/members")

      state = :sys.get_state(view.pid)
      assert state.socket.assigns.boolean_custom_fields == []
    end

    test "mount loads and filters boolean custom fields correctly", %{conn: conn} do
      conn = conn_with_oidc_user(conn)

      # Create boolean and non-boolean custom fields
      boolean_field1 = create_boolean_custom_field(%{name: "Active Member"})
      boolean_field2 = create_boolean_custom_field(%{name: "Newsletter Subscription"})
      _string_field = create_string_custom_field(%{name: "Phone Number"})

      {:ok, view, _html} = live(conn, "/members")

      state = :sys.get_state(view.pid)
      boolean_custom_fields = state.socket.assigns.boolean_custom_fields

      # Should only contain boolean fields
      assert length(boolean_custom_fields) == 2
      assert Enum.all?(boolean_custom_fields, &(&1.value_type == :boolean))
      assert Enum.any?(boolean_custom_fields, &(&1.id == boolean_field1.id))
      assert Enum.any?(boolean_custom_fields, &(&1.id == boolean_field2.id))
    end

    test "mount sorts boolean custom fields by name ascending", %{conn: conn} do
      conn = conn_with_oidc_user(conn)

      # Create boolean fields with specific names to test sorting
      _boolean_field_z = create_boolean_custom_field(%{name: "Zebra Field"})
      _boolean_field_a = create_boolean_custom_field(%{name: "Alpha Field"})
      _boolean_field_m = create_boolean_custom_field(%{name: "Middle Field"})

      {:ok, view, _html} = live(conn, "/members")

      state = :sys.get_state(view.pid)
      boolean_custom_fields = state.socket.assigns.boolean_custom_fields

      # Should be sorted by name ascending
      names = Enum.map(boolean_custom_fields, & &1.name)
      assert names == ["Alpha Field", "Middle Field", "Zebra Field"]
    end

    test "handle_params parses bf_<id> values correctly", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      boolean_field = create_boolean_custom_field()

      # Test true value
      {:ok, view1, _html} =
        live(conn, "/members?bf_#{boolean_field.id}=true")

      state1 = :sys.get_state(view1.pid)
      filters1 = state1.socket.assigns.boolean_custom_field_filters
      assert filters1[boolean_field.id] == true
      refute filters1[boolean_field.id] == "true"

      # Test false value
      {:ok, view2, _html} =
        live(conn, "/members?bf_#{boolean_field.id}=false")

      state2 = :sys.get_state(view2.pid)
      filters2 = state2.socket.assigns.boolean_custom_field_filters
      assert filters2[boolean_field.id] == false
      refute filters2[boolean_field.id] == "false"
    end

    test "handle_params ignores non-existent custom field IDs", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      fake_id = Ecto.UUID.generate()

      {:ok, view, _html} =
        live(conn, "/members?bf_#{fake_id}=true")

      state = :sys.get_state(view.pid)
      filters = state.socket.assigns.boolean_custom_field_filters

      # Filter should not be added for non-existent custom field
      refute Map.has_key?(filters, fake_id)
      assert filters == %{}
    end

    test "handle_params ignores non-boolean custom fields", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      string_field = create_string_custom_field()

      {:ok, view, _html} =
        live(conn, "/members?bf_#{string_field.id}=true")

      state = :sys.get_state(view.pid)
      filters = state.socket.assigns.boolean_custom_field_filters

      # Filter should not be added for non-boolean custom field
      refute Map.has_key?(filters, string_field.id)
      assert filters == %{}
    end

    test "handle_params ignores invalid filter values", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      boolean_field = create_boolean_custom_field()

      # Test various invalid values
      invalid_values = ["1", "0", "yes", "no", "True", "False", "", "invalid", "null"]

      for invalid_value <- invalid_values do
        {:ok, view, _html} =
          live(conn, "/members?bf_#{boolean_field.id}=#{invalid_value}")

        state = :sys.get_state(view.pid)
        filters = state.socket.assigns.boolean_custom_field_filters

        # Invalid values should not be added to filters
        refute Map.has_key?(filters, boolean_field.id),
               "Invalid value '#{invalid_value}' should not be added to filters"
      end
    end

    test "handle_params handles multiple boolean filters simultaneously", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      boolean_field1 = create_boolean_custom_field()
      boolean_field2 = create_boolean_custom_field()

      {:ok, view, _html} =
        live(
          conn,
          "/members?bf_#{boolean_field1.id}=true&bf_#{boolean_field2.id}=false"
        )

      state = :sys.get_state(view.pid)
      filters = state.socket.assigns.boolean_custom_field_filters

      assert filters[boolean_field1.id] == true
      assert filters[boolean_field2.id] == false
      assert map_size(filters) == 2
    end

    test "build_query_params includes active boolean filters and excludes nil filters", %{
      conn: conn
    } do
      conn = conn_with_oidc_user(conn)
      boolean_field1 = create_boolean_custom_field()
      boolean_field2 = create_boolean_custom_field()

      # Test with active filters
      {:ok, view1, _html} =
        live(
          conn,
          "/members?bf_#{boolean_field1.id}=true&bf_#{boolean_field2.id}=false"
        )

      # Trigger a search to see if filters are preserved in URL
      view1
      |> element("[data-testid='search-input']")
      |> render_change(%{value: "test"})

      # Check that the patch includes boolean filters
      path1 = assert_patch(view1)
      assert path1 =~ "bf_#{boolean_field1.id}=true"
      assert path1 =~ "bf_#{boolean_field2.id}=false"

      # Test without filters (nil filters should not appear in URL)
      {:ok, view2, _html} = live(conn, "/members")

      # Trigger a search
      view2
      |> element("[data-testid='search-input']")
      |> render_change(%{value: "test"})

      # Check that no bf_ params are in URL
      path2 = assert_patch(view2)
      refute path2 =~ "bf_"
    end

    test "boolean filters are preserved during navigation actions", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      boolean_field = create_boolean_custom_field()

      {:ok, view, _html} =
        live(conn, "/members?bf_#{boolean_field.id}=true")

      # Test sort toggle preserves filter
      view
      |> element("[data-testid='email']")
      |> render_click()

      path1 = assert_patch(view)
      assert path1 =~ "bf_#{boolean_field.id}=true"

      # Test search change preserves filter
      view
      |> element("[data-testid='search-input']")
      |> render_change(%{value: "test"})

      path2 = assert_patch(view)
      assert path2 =~ "bf_#{boolean_field.id}=true"
    end

    test "boolean filters work together with cycle_status_filter", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      boolean_field = create_boolean_custom_field()

      {:ok, view, _html} =
        live(
          conn,
          "/members?cycle_status_filter=paid&bf_#{boolean_field.id}=true"
        )

      state = :sys.get_state(view.pid)
      filters = state.socket.assigns.boolean_custom_field_filters

      # Both filters should be set
      assert filters[boolean_field.id] == true
      assert state.socket.assigns.cycle_status_filter == :paid

      # Both should be in URL when triggering search
      view
      |> element("[data-testid='search-input']")
      |> render_change(%{value: "test"})

      path = assert_patch(view)
      assert path =~ "cycle_status_filter=paid"
      assert path =~ "bf_#{boolean_field.id}=true"
    end

    test "handle_params removes filter when custom field is deleted", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      boolean_field = create_boolean_custom_field()

      # Set up filter via URL
      {:ok, view, _html} =
        live(conn, "/members?bf_#{boolean_field.id}=true")

      state_before = :sys.get_state(view.pid)
      filters_before = state_before.socket.assigns.boolean_custom_field_filters
      assert filters_before[boolean_field.id] == true

      # Delete the custom field (requires actor with destroy permission)
      Ash.destroy!(boolean_field, actor: system_actor)

      # Navigate again - filter should be removed since custom field no longer exists
      {:ok, view2, _html} =
        live(conn, "/members?bf_#{boolean_field.id}=true")

      state_after = :sys.get_state(view2.pid)
      filters_after = state_after.socket.assigns.boolean_custom_field_filters

      # Filter should not be present for deleted custom field
      refute Map.has_key?(filters_after, boolean_field.id)
      assert filters_after == %{}
    end

    test "handle_params handles URL-encoded custom field IDs correctly", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      boolean_field = create_boolean_custom_field()

      # URL-encode the custom field ID (though UUIDs shouldn't need encoding normally)
      encoded_id = URI.encode(boolean_field.id)

      {:ok, view, _html} =
        live(conn, "/members?bf_#{encoded_id}=true")

      state = :sys.get_state(view.pid)
      filters = state.socket.assigns.boolean_custom_field_filters

      # Filter should work with URL-encoded ID
      # Phoenix should decode it automatically, so we check with original ID
      assert filters[boolean_field.id] == true
    end

    test "handle_params ignores malformed prefix (bf_bf_<uuid>)", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      boolean_field = create_boolean_custom_field()

      # Try to send parameter with double prefix
      {:ok, view, _html} =
        live(conn, "/members?bf_bf_#{boolean_field.id}=true")

      state = :sys.get_state(view.pid)
      filters = state.socket.assigns.boolean_custom_field_filters

      # Should not parse as valid filter (UUID validation should fail)
      refute Map.has_key?(filters, boolean_field.id)
      assert filters == %{}
    end

    test "handle_params limits number of boolean filters to prevent DoS", %{conn: conn} do
      conn = conn_with_oidc_user(conn)

      # Create 60 boolean custom fields (more than the limit)
      boolean_fields = Enum.map(1..60, fn _ -> create_boolean_custom_field() end)

      # Build URL with all 60 filters
      filter_params =
        Enum.map_join(boolean_fields, "&", fn cf -> "bf_#{cf.id}=true" end)

      {:ok, view, _html} = live(conn, "/members?#{filter_params}")

      state = :sys.get_state(view.pid)
      filters = state.socket.assigns.boolean_custom_field_filters

      # Should limit to maximum 50 filters
      assert map_size(filters) <= 50
      # All filters in the result should be valid
      Enum.each(filters, fn {id, value} ->
        assert value in [true, false]
        # Verify the ID corresponds to one of our boolean fields
        assert id in Enum.map(boolean_fields, &to_string(&1.id))
      end)
    end

    test "handle_params ignores extremely long custom field IDs", %{conn: conn} do
      conn = conn_with_oidc_user(conn)
      boolean_field = create_boolean_custom_field()

      # Create a fake ID that's way too long (UUIDs are max 36 chars)
      fake_long_id = String.duplicate("a", 100)

      {:ok, view, _html} =
        live(conn, "/members?bf_#{fake_long_id}=true")

      state = :sys.get_state(view.pid)
      filters = state.socket.assigns.boolean_custom_field_filters

      # Should not accept the extremely long ID
      refute Map.has_key?(filters, fake_long_id)
      # Valid boolean field should still work
      refute Map.has_key?(filters, boolean_field.id)
      assert filters == %{}
    end

    # Helper to create a member with a boolean custom field value
    defp create_member_with_boolean_value(member_attrs, custom_field, value, actor) do
      attrs =
        %{
          first_name: "Test",
          last_name: "Member",
          email: "test.member.#{System.unique_integer([:positive])}@example.com"
        }
        |> Map.merge(member_attrs)

      {:ok, member} = Mv.Membership.create_member(attrs, actor: actor)

      {:ok, _cfv} =
        Mv.Membership.CustomFieldValue
        |> Ash.Changeset.for_create(:create, %{
          member_id: member.id,
          custom_field_id: custom_field.id,
          value: %{"_union_type" => "boolean", "_union_value" => value}
        })
        |> Ash.create(actor: actor)

      # Reload member with custom field values
      member
      |> Ash.load!(:custom_field_values, actor: actor)
    end

    # Tests for get_boolean_custom_field_value/2
    test "get_boolean_custom_field_value extracts true from Ash.Union format", %{conn: _conn} do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      boolean_field = create_boolean_custom_field()
      member = create_member_with_boolean_value(%{}, boolean_field, true, system_actor)

      # Test the function (will fail until implemented)
      result = MvWeb.MemberLive.Index.get_boolean_custom_field_value(member, boolean_field)

      assert result == true
    end

    test "get_boolean_custom_field_value extracts false from Ash.Union format", %{conn: _conn} do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      boolean_field = create_boolean_custom_field()
      member = create_member_with_boolean_value(%{}, boolean_field, false, system_actor)

      result = MvWeb.MemberLive.Index.get_boolean_custom_field_value(member, boolean_field)

      assert result == false
    end

    test "get_boolean_custom_field_value extracts true from map format with _union_type and _union_value keys",
         %{conn: _conn} do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      boolean_field = create_boolean_custom_field()

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

      # Create CustomFieldValue with map format (Ash expects _union_type and _union_value)
      {:ok, _cfv} =
        Mv.Membership.CustomFieldValue
        |> Ash.Changeset.for_create(:create, %{
          member_id: member.id,
          custom_field_id: boolean_field.id,
          value: %{"_union_type" => "boolean", "_union_value" => true}
        })
        |> Ash.create(actor: system_actor)

      # Reload member with custom field values
      member = member |> Ash.load!(:custom_field_values, actor: system_actor)

      result = MvWeb.MemberLive.Index.get_boolean_custom_field_value(member, boolean_field)

      assert result == true
    end

    test "get_boolean_custom_field_value returns nil when no CustomFieldValue exists", %{
      conn: _conn
    } do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      boolean_field = create_boolean_custom_field()

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

      # Member has no custom field value for this field
      member = member |> Ash.load!(:custom_field_values, actor: system_actor)

      result = MvWeb.MemberLive.Index.get_boolean_custom_field_value(member, boolean_field)

      assert result == nil
    end

    test "get_boolean_custom_field_value returns nil when CustomFieldValue has nil value", %{
      conn: _conn
    } do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      boolean_field = create_boolean_custom_field()

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

      # Create CustomFieldValue with nil value (edge case)
      {:ok, _cfv} =
        Mv.Membership.CustomFieldValue
        |> Ash.Changeset.for_create(:create, %{
          member_id: member.id,
          custom_field_id: boolean_field.id,
          value: nil
        })
        |> Ash.create(actor: system_actor)

      member = member |> Ash.load!(:custom_field_values, actor: system_actor)

      result = MvWeb.MemberLive.Index.get_boolean_custom_field_value(member, boolean_field)

      assert result == nil
    end

    test "get_boolean_custom_field_value returns nil for non-boolean CustomFieldValue", %{
      conn: _conn
    } do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      string_field = create_string_custom_field()
      boolean_field = create_boolean_custom_field()

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

      # Create string custom field value (not boolean)
      {:ok, _cfv} =
        Mv.Membership.CustomFieldValue
        |> Ash.Changeset.for_create(:create, %{
          member_id: member.id,
          custom_field_id: string_field.id,
          value: %{"_union_type" => "string", "_union_value" => "test"}
        })
        |> Ash.create(actor: system_actor)

      member = member |> Ash.load!(:custom_field_values, actor: system_actor)

      # Try to get boolean value from string field - should return nil
      result = MvWeb.MemberLive.Index.get_boolean_custom_field_value(member, boolean_field)

      assert result == nil
    end

    # Tests for apply_boolean_custom_field_filters/2
    test "apply_boolean_custom_field_filters filters members with true value and excludes false/without values",
         %{conn: _conn} do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      boolean_field = create_boolean_custom_field()

      member_with_true =
        create_member_with_boolean_value(
          %{first_name: "TrueMember"},
          boolean_field,
          true,
          system_actor
        )

      member_with_false =
        create_member_with_boolean_value(
          %{first_name: "FalseMember"},
          boolean_field,
          false,
          system_actor
        )

      {:ok, member_without_value} =
        Mv.Membership.create_member(
          %{
            first_name: "NoValue",
            last_name: "Member",
            email: "novalue.member.#{System.unique_integer([:positive])}@example.com"
          },
          actor: system_actor
        )

      member_without_value =
        member_without_value |> Ash.load!(:custom_field_values, actor: system_actor)

      members = [member_with_true, member_with_false, member_without_value]
      filters = %{to_string(boolean_field.id) => true}
      all_custom_fields = Mv.Membership.CustomField |> Ash.read!(actor: system_actor)

      result =
        MvWeb.MemberLive.Index.apply_boolean_custom_field_filters(
          members,
          filters,
          all_custom_fields
        )

      assert length(result) == 1
      assert List.first(result).id == member_with_true.id
      refute Enum.any?(result, &(&1.id == member_with_false.id))
      refute Enum.any?(result, &(&1.id == member_without_value.id))
    end

    test "apply_boolean_custom_field_filters filters members with false value and excludes true/without values",
         %{conn: _conn} do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      boolean_field = create_boolean_custom_field()

      member_with_true =
        create_member_with_boolean_value(
          %{first_name: "TrueMember"},
          boolean_field,
          true,
          system_actor
        )

      member_with_false =
        create_member_with_boolean_value(
          %{first_name: "FalseMember"},
          boolean_field,
          false,
          system_actor
        )

      {:ok, member_without_value} =
        Mv.Membership.create_member(
          %{
            first_name: "NoValue",
            last_name: "Member",
            email: "novalue.member.#{System.unique_integer([:positive])}@example.com"
          },
          actor: system_actor
        )

      member_without_value =
        member_without_value |> Ash.load!(:custom_field_values, actor: system_actor)

      members = [member_with_true, member_with_false, member_without_value]
      filters = %{to_string(boolean_field.id) => false}
      all_custom_fields = Mv.Membership.CustomField |> Ash.read!(actor: system_actor)

      result =
        MvWeb.MemberLive.Index.apply_boolean_custom_field_filters(
          members,
          filters,
          all_custom_fields
        )

      assert length(result) == 1
      assert List.first(result).id == member_with_false.id
      refute Enum.any?(result, &(&1.id == member_with_true.id))
      refute Enum.any?(result, &(&1.id == member_without_value.id))
    end

    test "apply_boolean_custom_field_filters returns all members when filter map is empty", %{
      conn: _conn
    } do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      boolean_field = create_boolean_custom_field()

      member1 =
        create_member_with_boolean_value(
          %{first_name: "Member1"},
          boolean_field,
          true,
          system_actor
        )

      member2 =
        create_member_with_boolean_value(
          %{first_name: "Member2"},
          boolean_field,
          false,
          system_actor
        )

      members = [member1, member2]
      filters = %{}
      all_custom_fields = Mv.Membership.CustomField |> Ash.read!(actor: system_actor)

      result =
        MvWeb.MemberLive.Index.apply_boolean_custom_field_filters(
          members,
          filters,
          all_custom_fields
        )

      assert length(result) == 2

      assert Enum.all?([member1.id, member2.id], fn id ->
               Enum.any?(result, &(&1.id == id))
             end)
    end

    test "apply_boolean_custom_field_filters applies multiple filters with AND logic", %{
      conn: _conn
    } do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      boolean_field1 = create_boolean_custom_field(%{name: "Field1"})
      boolean_field2 = create_boolean_custom_field(%{name: "Field2"})

      # Member with both fields = true
      {:ok, member_both_true} =
        Mv.Membership.create_member(
          %{
            first_name: "BothTrue",
            last_name: "Member",
            email: "bothtrue.member.#{System.unique_integer([:positive])}@example.com"
          },
          actor: system_actor
        )

      {:ok, _cfv1} =
        Mv.Membership.CustomFieldValue
        |> Ash.Changeset.for_create(:create, %{
          member_id: member_both_true.id,
          custom_field_id: boolean_field1.id,
          value: %{"_union_type" => "boolean", "_union_value" => true}
        })
        |> Ash.create(actor: system_actor)

      {:ok, _cfv2} =
        Mv.Membership.CustomFieldValue
        |> Ash.Changeset.for_create(:create, %{
          member_id: member_both_true.id,
          custom_field_id: boolean_field2.id,
          value: %{"_union_type" => "boolean", "_union_value" => true}
        })
        |> Ash.create(actor: system_actor)

      member_both_true = member_both_true |> Ash.load!(:custom_field_values, actor: system_actor)

      # Member with field1 = true, field2 = false
      {:ok, member_mixed} =
        Mv.Membership.create_member(
          %{
            first_name: "Mixed",
            last_name: "Member",
            email: "mixed.member.#{System.unique_integer([:positive])}@example.com"
          },
          actor: system_actor
        )

      {:ok, _cfv3} =
        Mv.Membership.CustomFieldValue
        |> Ash.Changeset.for_create(:create, %{
          member_id: member_mixed.id,
          custom_field_id: boolean_field1.id,
          value: %{"_union_type" => "boolean", "_union_value" => true}
        })
        |> Ash.create(actor: system_actor)

      {:ok, _cfv4} =
        Mv.Membership.CustomFieldValue
        |> Ash.Changeset.for_create(:create, %{
          member_id: member_mixed.id,
          custom_field_id: boolean_field2.id,
          value: %{"_union_type" => "boolean", "_union_value" => false}
        })
        |> Ash.create(actor: system_actor)

      member_mixed = member_mixed |> Ash.load!(:custom_field_values, actor: system_actor)

      members = [member_both_true, member_mixed]

      filters = %{
        to_string(boolean_field1.id) => true,
        to_string(boolean_field2.id) => true
      }

      all_custom_fields = Mv.Membership.CustomField |> Ash.read!(actor: system_actor)

      result =
        MvWeb.MemberLive.Index.apply_boolean_custom_field_filters(
          members,
          filters,
          all_custom_fields
        )

      # Only member_both_true should match (both fields = true)
      assert length(result) == 1
      assert List.first(result).id == member_both_true.id
    end

    test "apply_boolean_custom_field_filters ignores filter with non-existent custom field ID", %{
      conn: _conn
    } do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      boolean_field = create_boolean_custom_field()
      fake_id = Ecto.UUID.generate()

      member =
        create_member_with_boolean_value(
          %{first_name: "Member"},
          boolean_field,
          true,
          system_actor
        )

      members = [member]
      filters = %{fake_id => true}
      all_custom_fields = Mv.Membership.CustomField |> Ash.read!(actor: system_actor)

      result =
        MvWeb.MemberLive.Index.apply_boolean_custom_field_filters(
          members,
          filters,
          all_custom_fields
        )

      # Should return all members since fake_id doesn't match any custom field
      assert length(result) == 1
    end

    # Integration tests for boolean custom field filters in load_members
    test "boolean filter integration filters members by boolean custom field value via URL parameter",
         %{conn: conn} do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      conn = conn_with_oidc_user(conn)
      boolean_field = create_boolean_custom_field()

      _member_with_true =
        create_member_with_boolean_value(
          %{first_name: "TrueMember"},
          boolean_field,
          true,
          system_actor
        )

      _member_with_false =
        create_member_with_boolean_value(
          %{first_name: "FalseMember"},
          boolean_field,
          false,
          system_actor
        )

      {:ok, _member_without_value} =
        Mv.Membership.create_member(
          %{
            first_name: "NoValue",
            last_name: "Member",
            email: "novalue.member.#{System.unique_integer([:positive])}@example.com"
          },
          actor: system_actor
        )

      # Test true filter
      {:ok, _view, html_true} =
        live(conn, "/members?bf_#{boolean_field.id}=true")

      assert html_true =~ "TrueMember"
      refute html_true =~ "FalseMember"
      refute html_true =~ "NoValue"

      # Test false filter
      {:ok, _view, html_false} =
        live(conn, "/members?bf_#{boolean_field.id}=false")

      assert html_false =~ "FalseMember"
      refute html_false =~ "TrueMember"
      refute html_false =~ "NoValue"
    end

    test "boolean filter integration works together with cycle_status_filter", %{conn: conn} do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      conn = conn_with_oidc_user(conn)
      boolean_field = create_boolean_custom_field()
      fee_type = create_fee_type(%{interval: :yearly}, system_actor)
      today = Date.utc_today()
      last_year_start = Date.new!(today.year - 1, 1, 1)

      # Member with true boolean value and paid status
      {:ok, member_paid_true} =
        Mv.Membership.create_member(
          %{
            first_name: "PaidTrue",
            last_name: "Member",
            email: "paidtrue.member.#{System.unique_integer([:positive])}@example.com",
            membership_fee_type_id: fee_type.id
          },
          actor: system_actor
        )

      {:ok, _cfv} =
        Mv.Membership.CustomFieldValue
        |> Ash.Changeset.for_create(:create, %{
          member_id: member_paid_true.id,
          custom_field_id: boolean_field.id,
          value: %{"_union_type" => "boolean", "_union_value" => true}
        })
        |> Ash.create(actor: system_actor)

      create_cycle(
        member_paid_true,
        fee_type,
        %{cycle_start: last_year_start, status: :paid},
        system_actor
      )

      # Member with true boolean value but unpaid status
      {:ok, member_unpaid_true} =
        Mv.Membership.create_member(
          %{
            first_name: "UnpaidTrue",
            last_name: "Member",
            email: "unpaidtrue.member.#{System.unique_integer([:positive])}@example.com",
            membership_fee_type_id: fee_type.id
          },
          actor: system_actor
        )

      {:ok, _cfv2} =
        Mv.Membership.CustomFieldValue
        |> Ash.Changeset.for_create(:create, %{
          member_id: member_unpaid_true.id,
          custom_field_id: boolean_field.id,
          value: %{"_union_type" => "boolean", "_union_value" => true}
        })
        |> Ash.create(actor: system_actor)

      create_cycle(
        member_unpaid_true,
        fee_type,
        %{cycle_start: last_year_start, status: :unpaid},
        system_actor
      )

      # Test both filters together
      {:ok, _view, html} =
        live(conn, "/members?cycle_status_filter=paid&bf_#{boolean_field.id}=true")

      # Only member_paid_true should match both filters
      assert html =~ "PaidTrue"
      refute html =~ "UnpaidTrue"
    end

    test "boolean filter integration works together with search query", %{conn: conn} do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      conn = conn_with_oidc_user(conn)
      boolean_field = create_boolean_custom_field()

      _member_with_true =
        create_member_with_boolean_value(
          %{first_name: "TrueMember"},
          boolean_field,
          true,
          system_actor
        )

      _member_with_false =
        create_member_with_boolean_value(
          %{first_name: "FalseMember"},
          boolean_field,
          false,
          system_actor
        )

      # Test search + boolean filter
      {:ok, _view, html} =
        live(conn, "/members?query=TrueMember&bf_#{boolean_field.id}=true")

      # Only member_with_true should match both search and filter
      assert html =~ "TrueMember"
      refute html =~ "FalseMember"
    end

    test "boolean filter works even when custom field is not visible in overview", %{conn: conn} do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      conn = conn_with_oidc_user(conn)

      # Create boolean field with show_in_overview: false
      boolean_field = create_boolean_custom_field(%{show_in_overview: false})

      _member_with_true =
        create_member_with_boolean_value(
          %{first_name: "TrueMember"},
          boolean_field,
          true,
          system_actor
        )

      _member_with_false =
        create_member_with_boolean_value(
          %{first_name: "FalseMember"},
          boolean_field,
          false,
          system_actor
        )

      {:ok, _member_without_value} =
        Mv.Membership.create_member(
          %{
            first_name: "NoValue",
            last_name: "Member",
            email: "novalue.member.#{System.unique_integer([:positive])}@example.com"
          },
          actor: system_actor
        )

      # Test that filter works even though field is not visible in overview
      {:ok, _view, html_true} =
        live(conn, "/members?bf_#{boolean_field.id}=true")

      assert html_true =~ "TrueMember"
      refute html_true =~ "FalseMember"
      refute html_true =~ "NoValue"

      # Test false filter
      {:ok, _view, html_false} =
        live(conn, "/members?bf_#{boolean_field.id}=false")

      assert html_false =~ "FalseMember"
      refute html_false =~ "TrueMember"
      refute html_false =~ "NoValue"
    end

    @tag :ui
    test "boolean custom field appears in filter dropdown after being added", %{conn: conn} do
      conn = conn_with_oidc_user(conn)

      # Start with no boolean custom fields
      {:ok, view, _html} = live(conn, "/members")

      state_before = :sys.get_state(view.pid)
      boolean_fields_before = state_before.socket.assigns.boolean_custom_fields
      assert boolean_fields_before == []

      # Create a new boolean custom field
      new_boolean_field = create_boolean_custom_field(%{name: "Newly Added Field"})

      # Navigate again - the new field should appear
      {:ok, view2, _html} = live(conn, "/members")

      state_after = :sys.get_state(view2.pid)
      boolean_fields_after = state_after.socket.assigns.boolean_custom_fields

      # New boolean field should be present
      assert length(boolean_fields_after) == 1
      assert Enum.any?(boolean_fields_after, &(&1.id == new_boolean_field.id))
      assert Enum.any?(boolean_fields_after, &(&1.name == "Newly Added Field"))
    end

    @tag :slow
    test "boolean filter performance with 150 members", %{conn: conn} do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      conn = conn_with_oidc_user(conn)
      boolean_field = create_boolean_custom_field()

      # Create 150 members - 75 with true, 75 with false
      members_with_true =
        Enum.map(1..75, fn i ->
          create_member_with_boolean_value(
            %{
              first_name: "TrueMember#{i}",
              email: "truemember#{i}@example.com"
            },
            boolean_field,
            true,
            system_actor
          )
        end)

      members_with_false =
        Enum.map(1..75, fn i ->
          create_member_with_boolean_value(
            %{
              first_name: "FalseMember#{i}",
              email: "falsemember#{i}@example.com"
            },
            boolean_field,
            false,
            system_actor
          )
        end)

      # Verify all members were created
      assert length(members_with_true) == 75
      assert length(members_with_false) == 75

      # Test filter performance - should complete in reasonable time (< 1 second)
      start_time = System.monotonic_time(:millisecond)

      {:ok, _view, html} =
        live(conn, "/members?bf_#{boolean_field.id}=true")

      end_time = System.monotonic_time(:millisecond)
      duration = end_time - start_time

      # Should complete in less than 1 second (1000ms)
      assert duration < 1000, "Filter took #{duration}ms, expected < 1000ms"

      # Verify filtering worked correctly - should show all true members
      Enum.each(1..75, fn i ->
        assert html =~ "TrueMember#{i}"
      end)

      # Should not show false members
      Enum.each(1..75, fn i ->
        refute html =~ "FalseMember#{i}"
      end)
    end
  end
end
