defmodule Mv.Membership.MemberFieldVisibilityTest do
  @moduledoc """
  Tests for member field visibility configuration.

  Tests cover:
  - Member fields are visible by default (show_in_overview: true)
  - Member fields can be hidden (show_in_overview: false)
  - Checking if a specific field is visible
  - Configuration is stored in Settings resource
  """
  use Mv.DataCase, async: true

  alias Mv.Membership.Member

  describe "show_in_overview?/1" do
    test "returns true for all member fields by default, except exit_date" do
      # When no settings exist or member_field_visibility is not configured
      # Test with fields from constants
      # Note: exit_date defaults to false (hidden) by design
      member_fields = Mv.Constants.member_fields()

      Enum.each(member_fields, fn field ->
        expected_visibility = if field == :exit_date, do: false, else: true

        assert Member.show_in_overview?(field) == expected_visibility,
               "Field #{field} should be #{if expected_visibility, do: "visible", else: "hidden"} by default"
      end)
    end

    test "returns false for fields with show_in_overview: false in settings" do
      # Get or create settings
      {:ok, settings} = Mv.Membership.get_settings()

      # Use a field that exists in member fields
      member_fields = Mv.Constants.member_fields()
      field_to_hide = List.first(member_fields)
      field_to_show = List.last(member_fields)

      # Update settings to hide a field (use string keys for JSONB)
      {:ok, _updated_settings} =
        Mv.Membership.update_settings(settings, %{
          member_field_visibility: %{Atom.to_string(field_to_hide) => false}
        })

      # JSONB may convert atom keys to string keys, so we check via show_in_overview? instead
      assert Member.show_in_overview?(field_to_hide) == false
      assert Member.show_in_overview?(field_to_show) == true
    end

    test "returns true for non-configured fields (default)" do
      # Get or create settings
      {:ok, settings} = Mv.Membership.get_settings()

      # Use fields that exist in member fields
      member_fields = Mv.Constants.member_fields()
      fields_to_hide = Enum.take(member_fields, 2)
      fields_to_show = Enum.take(member_fields, -2)

      # Update settings to hide some fields (use string keys for JSONB)
      visibility_config =
        Enum.reduce(fields_to_hide, %{}, fn field, acc ->
          Map.put(acc, Atom.to_string(field), false)
        end)

      {:ok, _updated_settings} =
        Mv.Membership.update_settings(settings, %{
          member_field_visibility: visibility_config
        })

      # Hidden fields should be false
      Enum.each(fields_to_hide, fn field ->
        assert Member.show_in_overview?(field) == false,
               "Field #{field} should be hidden"
      end)

      # Unconfigured fields should still be true (default)
      Enum.each(fields_to_show, fn field ->
        assert Member.show_in_overview?(field) == true,
               "Field #{field} should be visible by default"
      end)
    end
  end

  describe "update_single_member_field_visibility/3" do
    test "atomically updates a single field in member_field_visibility" do
      {:ok, settings} = Mv.Membership.get_settings()
      field_string = "street"

      # Update single field
      {:ok, updated_settings} =
        Mv.Membership.update_single_member_field_visibility(
          settings,
          field: field_string,
          show_in_overview: false
        )

      # Verify the field was updated
      assert updated_settings.member_field_visibility[field_string] == false

      # Verify other fields are not affected
      other_fields =
        Mv.Constants.member_fields()
        |> Enum.reject(&(&1 == String.to_existing_atom(field_string)))

      Enum.each(other_fields, fn field ->
        field_string = Atom.to_string(field)
        # Fields not explicitly set should default to true (except exit_date)
        expected = if field == :exit_date, do: false, else: true

        assert Map.get(updated_settings.member_field_visibility, field_string, expected) ==
                 expected
      end)
    end

    test "returns error for invalid field name" do
      {:ok, settings} = Mv.Membership.get_settings()

      assert {:error, %Ash.Error.Invalid{errors: [%{field: :member_field_visibility}]}} =
               Mv.Membership.update_single_member_field_visibility(
                 settings,
                 field: "invalid_field",
                 show_in_overview: false
               )
    end

    test "handles concurrent updates atomically" do
      {:ok, settings} = Mv.Membership.get_settings()
      field1 = "street"
      field2 = "house_number"

      # Simulate concurrent updates by updating different fields
      {:ok, updated1} =
        Mv.Membership.update_single_member_field_visibility(
          settings,
          field: field1,
          show_in_overview: false
        )

      {:ok, updated2} =
        Mv.Membership.update_single_member_field_visibility(
          updated1,
          field: field2,
          show_in_overview: true
        )

      # Both fields should be correctly updated
      assert updated2.member_field_visibility[field1] == false
      assert updated2.member_field_visibility[field2] == true
    end
  end
end
