defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
  @moduledoc """
  Tests for sorting by custom fields in the member overview.

  Tests cover:
  - Sorting by custom field (ascending)
  - Sorting by custom field (descending)
  - Sorting by custom field works with search
  - Sorting by custom field works with URL parameters
  - Sorting by custom field works with other columns
  """
  use MvWeb.ConnCase, async: true
  import Phoenix.LiveViewTest
  require Ash.Query

  alias Mv.Membership.{CustomField, CustomFieldValue}

  setup do
    system_actor = Mv.Helpers.SystemActor.get_system_actor()

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

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

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

    # Create custom field with show_in_overview: true
    {:ok, field_string} =
      CustomField
      |> Ash.Changeset.for_create(:create, %{
        name: "membership_number",
        value_type: :string,
        show_in_overview: true
      })
      |> Ash.create(actor: system_actor)

    {:ok, field_integer} =
      CustomField
      |> Ash.Changeset.for_create(:create, %{
        name: "priority",
        value_type: :integer,
        show_in_overview: true
      })
      |> Ash.create(actor: system_actor)

    # Create custom field values
    {:ok, _cfv1} =
      CustomFieldValue
      |> Ash.Changeset.for_create(:create, %{
        member_id: member1.id,
        custom_field_id: field_string.id,
        value: %{"_union_type" => "string", "_union_value" => "A001"}
      })
      |> Ash.create(actor: system_actor)

    {:ok, _cfv2} =
      CustomFieldValue
      |> Ash.Changeset.for_create(:create, %{
        member_id: member2.id,
        custom_field_id: field_string.id,
        value: %{"_union_type" => "string", "_union_value" => "C003"}
      })
      |> Ash.create(actor: system_actor)

    {:ok, _cfv3} =
      CustomFieldValue
      |> Ash.Changeset.for_create(:create, %{
        member_id: member3.id,
        custom_field_id: field_string.id,
        value: %{"_union_type" => "string", "_union_value" => "B002"}
      })
      |> Ash.create(actor: system_actor)

    {:ok, _cfv4} =
      CustomFieldValue
      |> Ash.Changeset.for_create(:create, %{
        member_id: member1.id,
        custom_field_id: field_integer.id,
        value: %{"_union_type" => "integer", "_union_value" => 10}
      })
      |> Ash.create(actor: system_actor)

    {:ok, _cfv5} =
      CustomFieldValue
      |> Ash.Changeset.for_create(:create, %{
        member_id: member2.id,
        custom_field_id: field_integer.id,
        value: %{"_union_type" => "integer", "_union_value" => 30}
      })
      |> Ash.create(actor: system_actor)

    {:ok, _cfv6} =
      CustomFieldValue
      |> Ash.Changeset.for_create(:create, %{
        member_id: member3.id,
        custom_field_id: field_integer.id,
        value: %{"_union_type" => "integer", "_union_value" => 20}
      })
      |> Ash.create(actor: system_actor)

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

  test "sorts by custom field ascending", %{conn: conn, field_string: field} do
    conn = conn_with_oidc_user(conn)
    {:ok, view, _html} = live(conn, "/members")

    # Click on custom field column header to sort
    view
    |> element("[data-testid='custom_field_#{field.id}']")
    |> render_click()

    # Check URL was updated
    assert_patch(view, "/members?query=&sort_field=custom_field_#{field.id}&sort_order=asc")

    # Verify sort state
    assert has_element?(view, "[data-testid='custom_field_#{field.id}'][aria-label='ascending']")
  end

  test "sorts by custom field descending", %{conn: conn, field_string: field} do
    conn = conn_with_oidc_user(conn)
    {:ok, view, _html} = live(conn, "/members?sort_field=custom_field_#{field.id}&sort_order=asc")

    # Click again to toggle to descending
    view
    |> element("[data-testid='custom_field_#{field.id}']")
    |> render_click()

    # Check URL was updated (param order may vary)
    path = assert_patch(view)
    assert path =~ "sort_order=desc"
    assert path =~ "sort_field=custom_field_#{field.id}"

    # Verify sort state
    assert has_element?(view, "[data-testid='custom_field_#{field.id}'][aria-label='descending']")
  end

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

    # Click on custom field column header to sort
    view
    |> element("[data-testid='custom_field_#{field.id}']")
    |> render_click()

    # Check URL maintains search query
    assert_patch(view, "/members?query=Alice&sort_field=custom_field_#{field.id}&sort_order=asc")
  end

  test "sorting by custom field works with URL parameters", %{conn: conn, field_string: field} do
    conn = conn_with_oidc_user(conn)

    {:ok, view, _html} =
      live(conn, "/members?query=&sort_field=custom_field_#{field.id}&sort_order=desc")

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

  test "clicking different custom field column resets order to ascending", %{
    conn: conn,
    field_string: field_string,
    field_integer: field_integer
  } do
    conn = conn_with_oidc_user(conn)

    {:ok, view, _html} =
      live(conn, "/members?query=&sort_field=custom_field_#{field_string.id}&sort_order=desc")

    # Click on a different custom field column
    view
    |> element("[data-testid='custom_field_#{field_integer.id}']")
    |> render_click()

    assert_patch(
      view,
      "/members?query=&sort_field=custom_field_#{field_integer.id}&sort_order=asc"
    )
  end

  test "clicking regular column after custom field column works", %{
    conn: conn,
    field_string: field
  } do
    conn = conn_with_oidc_user(conn)

    {:ok, view, _html} =
      live(conn, "/members?query=&sort_field=custom_field_#{field.id}&sort_order=desc")

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

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

  test "clicking custom field column after regular column works", %{
    conn: conn,
    field_string: field
  } do
    conn = conn_with_oidc_user(conn)
    {:ok, view, _html} = live(conn, "/members?query=&sort_field=email&sort_order=desc")

    # Click on custom field column
    view
    |> element("[data-testid='custom_field_#{field.id}']")
    |> render_click()

    # Patch URL may include fields param (current field selection); assert sort outcome instead
    assert has_element?(view, "[data-testid='custom_field_#{field.id}'][aria-label='ascending']")
  end

  test "NULL values and empty strings are always sorted last (ASC)", %{conn: conn} do
    system_actor = Mv.Helpers.SystemActor.get_system_actor()

    # Create additional members with NULL and empty string values
    {:ok, member_with_value} =
      Mv.Membership.create_member(
        %{first_name: "WithValue", last_name: "Test", email: "withvalue@example.com"},
        actor: system_actor
      )

    {:ok, member_with_empty} =
      Mv.Membership.create_member(
        %{first_name: "WithEmpty", last_name: "Test", email: "withempty@example.com"},
        actor: system_actor
      )

    {:ok, member_with_null} =
      Mv.Membership.create_member(
        %{first_name: "WithNull", last_name: "Test", email: "withnull@example.com"},
        actor: system_actor
      )

    {:ok, member_with_another_value} =
      Mv.Membership.create_member(
        %{first_name: "AnotherValue", last_name: "Test", email: "another@example.com"},
        actor: system_actor
      )

    # Create custom field
    {:ok, field} =
      CustomField
      |> Ash.Changeset.for_create(:create, %{
        name: "test_field",
        value_type: :string,
        show_in_overview: true
      })
      |> Ash.create(actor: system_actor)

    # Create values: one with actual value, one with empty string, one with NULL (no value), another with value
    {:ok, _cfv1} =
      CustomFieldValue
      |> Ash.Changeset.for_create(:create, %{
        member_id: member_with_value.id,
        custom_field_id: field.id,
        value: %{"_union_type" => "string", "_union_value" => "Zebra"}
      })
      |> Ash.create(actor: system_actor)

    {:ok, _cfv2} =
      CustomFieldValue
      |> Ash.Changeset.for_create(:create, %{
        member_id: member_with_empty.id,
        custom_field_id: field.id,
        value: %{"_union_type" => "string", "_union_value" => ""}
      })
      |> Ash.create(actor: system_actor)

    # member_with_null has no custom field value (NULL)

    {:ok, _cfv3} =
      CustomFieldValue
      |> Ash.Changeset.for_create(:create, %{
        member_id: member_with_another_value.id,
        custom_field_id: field.id,
        value: %{"_union_type" => "string", "_union_value" => "Apple"}
      })
      |> Ash.create(actor: system_actor)

    conn = conn_with_oidc_user(conn)

    {:ok, view, _html} =
      live(conn, "/members?query=&sort_field=custom_field_#{field.id}&sort_order=asc")

    html = render(view)

    # Find positions of member first names in the HTML to verify sort order
    apple_pos = :binary.match(html, member_with_another_value.first_name)
    zebra_pos = :binary.match(html, member_with_value.first_name)
    empty_pos = :binary.match(html, member_with_empty.first_name)
    null_pos = :binary.match(html, member_with_null.first_name)

    assert apple_pos != :nomatch, "AnotherValue (Apple) should be in HTML"
    assert zebra_pos != :nomatch, "WithValue (Zebra) should be in HTML"
    assert empty_pos != :nomatch, "WithEmpty should be in HTML"
    assert null_pos != :nomatch, "WithNull should be in HTML"

    {apple_idx, _} = apple_pos
    {zebra_idx, _} = zebra_pos
    {empty_idx, _} = empty_pos
    {null_idx, _} = null_pos

    # In ASC order: Apple should come before Zebra
    assert apple_idx < zebra_idx, "Apple should come before Zebra in ASC order"

    # NULL and empty should come after all values
    assert apple_idx < empty_idx, "Apple should come before empty value"
    assert apple_idx < null_idx, "Apple should come before NULL value"
    assert zebra_idx < empty_idx, "Zebra should come before empty value"
    assert zebra_idx < null_idx, "Zebra should come before NULL value"
  end

  test "NULL values and empty strings are always sorted last (DESC)", %{conn: conn} do
    system_actor = Mv.Helpers.SystemActor.get_system_actor()

    # Create additional members with NULL and empty string values
    {:ok, member_with_value} =
      Mv.Membership.create_member(
        %{first_name: "WithValue", last_name: "Test", email: "withvalue@example.com"},
        actor: system_actor
      )

    {:ok, member_with_empty} =
      Mv.Membership.create_member(
        %{first_name: "WithEmpty", last_name: "Test", email: "withempty@example.com"},
        actor: system_actor
      )

    {:ok, member_with_null} =
      Mv.Membership.create_member(
        %{first_name: "WithNull", last_name: "Test", email: "withnull@example.com"},
        actor: system_actor
      )

    {:ok, member_with_another_value} =
      Mv.Membership.create_member(
        %{first_name: "AnotherValue", last_name: "Test", email: "another@example.com"},
        actor: system_actor
      )

    # Create custom field
    {:ok, field} =
      CustomField
      |> Ash.Changeset.for_create(:create, %{
        name: "test_field",
        value_type: :string,
        show_in_overview: true
      })
      |> Ash.create(actor: system_actor)

    # Create values: one with actual value, one with empty string, one with NULL (no value), another with value
    {:ok, _cfv1} =
      CustomFieldValue
      |> Ash.Changeset.for_create(:create, %{
        member_id: member_with_value.id,
        custom_field_id: field.id,
        value: %{"_union_type" => "string", "_union_value" => "Apple"}
      })
      |> Ash.create(actor: system_actor)

    {:ok, _cfv2} =
      CustomFieldValue
      |> Ash.Changeset.for_create(:create, %{
        member_id: member_with_empty.id,
        custom_field_id: field.id,
        value: %{"_union_type" => "string", "_union_value" => ""}
      })
      |> Ash.create(actor: system_actor)

    # member_with_null has no custom field value (NULL)

    {:ok, _cfv3} =
      CustomFieldValue
      |> Ash.Changeset.for_create(:create, %{
        member_id: member_with_another_value.id,
        custom_field_id: field.id,
        value: %{"_union_type" => "string", "_union_value" => "Zebra"}
      })
      |> Ash.create(actor: system_actor)

    conn = conn_with_oidc_user(conn)

    {:ok, view, _html} =
      live(conn, "/members?query=&sort_field=custom_field_#{field.id}&sort_order=desc")

    html = render(view)

    # Find positions of member first names in the HTML to verify sort order
    apple_pos = :binary.match(html, member_with_value.first_name)
    zebra_pos = :binary.match(html, member_with_another_value.first_name)
    empty_pos = :binary.match(html, member_with_empty.first_name)
    null_pos = :binary.match(html, member_with_null.first_name)

    assert apple_pos != :nomatch, "WithValue (Apple) should be in HTML"
    assert zebra_pos != :nomatch, "AnotherValue (Zebra) should be in HTML"
    assert empty_pos != :nomatch, "WithEmpty should be in HTML"
    assert null_pos != :nomatch, "WithNull should be in HTML"

    {apple_idx, _} = apple_pos
    {zebra_idx, _} = zebra_pos
    {empty_idx, _} = empty_pos
    {null_idx, _} = null_pos

    # In DESC order: Zebra should come before Apple
    assert zebra_idx < apple_idx, "Zebra should come before Apple in DESC order"

    # NULL and empty should come after all values
    assert zebra_idx < empty_idx, "Zebra should come before empty value"
    assert zebra_idx < null_idx, "Zebra should come before NULL value"
    assert apple_idx < empty_idx, "Apple should come before empty value"
    assert apple_idx < null_idx, "Apple should come before NULL value"
  end
end
