defmodule Mv.Membership.MemberRequiredCustomFieldsTest do
  @moduledoc """
  Tests for required custom fields validation.

  Tests cover:
  - Member creation without required custom field → error
  - Member creation with empty required custom field (nil/empty string) → error
  - Member creation with valid required custom field → success
  - Member update: removing a required custom field value → error
  - Boolean required custom field: false is valid, nil is invalid
  """
  use Mv.DataCase, async: true

  alias Mv.Membership

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

    # Create required custom fields for different types
    {:ok, required_string_field} =
      Membership.CustomField
      |> Ash.Changeset.for_create(:create, %{
        name: "required_string",
        value_type: :string,
        required: true
      })
      |> Ash.create(actor: system_actor)

    {:ok, required_integer_field} =
      Membership.CustomField
      |> Ash.Changeset.for_create(:create, %{
        name: "required_integer",
        value_type: :integer,
        required: true
      })
      |> Ash.create(actor: system_actor)

    {:ok, required_boolean_field} =
      Membership.CustomField
      |> Ash.Changeset.for_create(:create, %{
        name: "required_boolean",
        value_type: :boolean,
        required: true
      })
      |> Ash.create(actor: system_actor)

    {:ok, required_date_field} =
      Membership.CustomField
      |> Ash.Changeset.for_create(:create, %{
        name: "required_date",
        value_type: :date,
        required: true
      })
      |> Ash.create(actor: system_actor)

    {:ok, required_email_field} =
      Membership.CustomField
      |> Ash.Changeset.for_create(:create, %{
        name: "required_email",
        value_type: :email,
        required: true
      })
      |> Ash.create(actor: system_actor)

    {:ok, optional_field} =
      Membership.CustomField
      |> Ash.Changeset.for_create(:create, %{
        name: "optional_string",
        value_type: :string,
        required: false
      })
      |> Ash.create(actor: system_actor)

    %{
      required_string_field: required_string_field,
      required_integer_field: required_integer_field,
      required_boolean_field: required_boolean_field,
      required_date_field: required_date_field,
      required_email_field: required_email_field,
      optional_field: optional_field,
      actor: system_actor
    }
  end

  # Helper function to create all required custom fields with valid default values
  defp all_required_custom_fields_with_defaults(%{
         required_string_field: string_field,
         required_integer_field: integer_field,
         required_boolean_field: boolean_field,
         required_date_field: date_field,
         required_email_field: email_field
       }) do
    [
      %{
        "custom_field_id" => string_field.id,
        "value" => %{"_union_type" => "string", "_union_value" => "default"}
      },
      %{
        "custom_field_id" => integer_field.id,
        "value" => %{"_union_type" => "integer", "_union_value" => 0}
      },
      %{
        "custom_field_id" => boolean_field.id,
        "value" => %{"_union_type" => "boolean", "_union_value" => false}
      },
      %{
        "custom_field_id" => date_field.id,
        "value" => %{"_union_type" => "date", "_union_value" => ~D[2020-01-01]}
      },
      %{
        "custom_field_id" => email_field.id,
        "value" => %{"_union_type" => "email", "_union_value" => "test@example.com"}
      }
    ]
  end

  describe "create_member with required custom fields" do
    @valid_attrs %{
      first_name: "John",
      last_name: "Doe",
      email: "john@example.com"
    }

    test "fails when required custom field is missing", %{
      required_string_field: field,
      actor: actor
    } do
      attrs = Map.put(@valid_attrs, :custom_field_values, [])

      assert {:error, %Ash.Error.Invalid{errors: errors}} =
               Membership.create_member(attrs, actor: actor)

      assert error_message(errors, :custom_field_values) =~ "Required custom fields missing"
      assert error_message(errors, :custom_field_values) =~ field.name
    end

    test "fails when required string custom field has nil value",
         %{
           required_string_field: field,
           actor: actor
         } = context do
      # Start with all required fields having valid values
      custom_field_values =
        all_required_custom_fields_with_defaults(context)
        |> Enum.map(fn cfv ->
          if cfv["custom_field_id"] == field.id do
            %{cfv | "value" => %{"_union_type" => "string", "_union_value" => nil}}
          else
            cfv
          end
        end)

      attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)

      assert {:error, %Ash.Error.Invalid{errors: errors}} =
               Membership.create_member(attrs, actor: actor)

      assert error_message(errors, :custom_field_values) =~ "Required custom fields missing"
      assert error_message(errors, :custom_field_values) =~ field.name
    end

    test "fails when required string custom field has empty string value",
         %{
           required_string_field: field,
           actor: actor
         } = context do
      # Start with all required fields having valid values
      custom_field_values =
        all_required_custom_fields_with_defaults(context)
        |> Enum.map(fn cfv ->
          if cfv["custom_field_id"] == field.id do
            %{cfv | "value" => %{"_union_type" => "string", "_union_value" => ""}}
          else
            cfv
          end
        end)

      attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)

      assert {:error, %Ash.Error.Invalid{errors: errors}} =
               Membership.create_member(attrs, actor: actor)

      assert error_message(errors, :custom_field_values) =~ "Required custom fields missing"
      assert error_message(errors, :custom_field_values) =~ field.name
    end

    test "fails when required string custom field has whitespace-only value",
         %{
           required_string_field: field,
           actor: actor
         } = context do
      # Start with all required fields having valid values
      custom_field_values =
        all_required_custom_fields_with_defaults(context)
        |> Enum.map(fn cfv ->
          if cfv["custom_field_id"] == field.id do
            %{cfv | "value" => %{"_union_type" => "string", "_union_value" => "   "}}
          else
            cfv
          end
        end)

      attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)

      assert {:error, %Ash.Error.Invalid{errors: errors}} =
               Membership.create_member(attrs, actor: actor)

      assert error_message(errors, :custom_field_values) =~ "Required custom fields missing"
      assert error_message(errors, :custom_field_values) =~ field.name
    end

    test "succeeds when required string custom field has valid value",
         %{
           required_string_field: field,
           actor: actor
         } = context do
      # Start with all required fields having valid values, then update the string field
      custom_field_values =
        all_required_custom_fields_with_defaults(context)
        |> Enum.map(fn cfv ->
          if cfv["custom_field_id"] == field.id do
            %{cfv | "value" => %{"_union_type" => "string", "_union_value" => "test value"}}
          else
            cfv
          end
        end)

      attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)

      assert {:ok, _member} = Membership.create_member(attrs, actor: actor)
    end

    test "fails when required integer custom field has nil value",
         %{
           required_integer_field: field,
           actor: actor
         } = context do
      custom_field_values =
        all_required_custom_fields_with_defaults(context)
        |> Enum.map(fn cfv ->
          if cfv["custom_field_id"] == field.id do
            %{cfv | "value" => %{"_union_type" => "integer", "_union_value" => nil}}
          else
            cfv
          end
        end)

      attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)

      assert {:error, %Ash.Error.Invalid{errors: errors}} =
               Membership.create_member(attrs, actor: actor)

      assert error_message(errors, :custom_field_values) =~ "Required custom fields missing"
      assert error_message(errors, :custom_field_values) =~ field.name
    end

    test "fails when required integer custom field has empty string value",
         %{
           required_integer_field: field,
           actor: actor
         } = context do
      custom_field_values =
        all_required_custom_fields_with_defaults(context)
        |> Enum.map(fn cfv ->
          if cfv["custom_field_id"] == field.id do
            %{cfv | "value" => %{"_union_type" => "integer", "_union_value" => ""}}
          else
            cfv
          end
        end)

      attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)

      assert {:error, %Ash.Error.Invalid{errors: errors}} =
               Membership.create_member(attrs, actor: actor)

      assert error_message(errors, :custom_field_values) =~ "Required custom fields missing"
      assert error_message(errors, :custom_field_values) =~ field.name
    end

    test "succeeds when required integer custom field has zero value",
         %{
           required_integer_field: _field,
           actor: actor
         } = context do
      custom_field_values = all_required_custom_fields_with_defaults(context)

      attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)

      assert {:ok, _member} = Membership.create_member(attrs, actor: actor)
    end

    test "succeeds when required integer custom field has positive value",
         %{
           required_integer_field: field,
           actor: actor
         } = context do
      custom_field_values =
        all_required_custom_fields_with_defaults(context)
        |> Enum.map(fn cfv ->
          if cfv["custom_field_id"] == field.id do
            %{cfv | "value" => %{"_union_type" => "integer", "_union_value" => 42}}
          else
            cfv
          end
        end)

      attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)

      assert {:ok, _member} = Membership.create_member(attrs, actor: actor)
    end

    test "fails when required boolean custom field has nil value",
         %{
           required_boolean_field: field,
           actor: actor
         } = context do
      custom_field_values =
        all_required_custom_fields_with_defaults(context)
        |> Enum.map(fn cfv ->
          if cfv["custom_field_id"] == field.id do
            %{cfv | "value" => %{"_union_type" => "boolean", "_union_value" => nil}}
          else
            cfv
          end
        end)

      attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)

      assert {:error, %Ash.Error.Invalid{errors: errors}} =
               Membership.create_member(attrs, actor: actor)

      assert error_message(errors, :custom_field_values) =~ "Required custom fields missing"
      assert error_message(errors, :custom_field_values) =~ field.name
    end

    test "succeeds when required boolean custom field has false value",
         %{
           required_boolean_field: _field,
           actor: actor
         } = context do
      custom_field_values = all_required_custom_fields_with_defaults(context)

      attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)

      assert {:ok, _member} = Membership.create_member(attrs, actor: actor)
    end

    test "succeeds when required boolean custom field has true value",
         %{
           required_boolean_field: field,
           actor: actor
         } = context do
      custom_field_values =
        all_required_custom_fields_with_defaults(context)
        |> Enum.map(fn cfv ->
          if cfv["custom_field_id"] == field.id do
            %{cfv | "value" => %{"_union_type" => "boolean", "_union_value" => true}}
          else
            cfv
          end
        end)

      attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)

      assert {:ok, _member} = Membership.create_member(attrs, actor: actor)
    end

    test "fails when required date custom field has nil value",
         %{
           required_date_field: field,
           actor: actor
         } = context do
      custom_field_values =
        all_required_custom_fields_with_defaults(context)
        |> Enum.map(fn cfv ->
          if cfv["custom_field_id"] == field.id do
            %{cfv | "value" => %{"_union_type" => "date", "_union_value" => nil}}
          else
            cfv
          end
        end)

      attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)

      assert {:error, %Ash.Error.Invalid{errors: errors}} =
               Membership.create_member(attrs, actor: actor)

      assert error_message(errors, :custom_field_values) =~ "Required custom fields missing"
      assert error_message(errors, :custom_field_values) =~ field.name
    end

    test "fails when required date custom field has empty string value",
         %{
           required_date_field: field,
           actor: actor
         } = context do
      custom_field_values =
        all_required_custom_fields_with_defaults(context)
        |> Enum.map(fn cfv ->
          if cfv["custom_field_id"] == field.id do
            %{cfv | "value" => %{"_union_type" => "date", "_union_value" => ""}}
          else
            cfv
          end
        end)

      attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)

      assert {:error, %Ash.Error.Invalid{errors: errors}} =
               Membership.create_member(attrs, actor: actor)

      assert error_message(errors, :custom_field_values) =~ "Required custom fields missing"
      assert error_message(errors, :custom_field_values) =~ field.name
    end

    test "succeeds when required date custom field has valid date value",
         %{
           required_date_field: _field,
           actor: actor
         } = context do
      custom_field_values = all_required_custom_fields_with_defaults(context)

      attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)

      assert {:ok, _member} = Membership.create_member(attrs, actor: actor)
    end

    test "fails when required email custom field has nil value",
         %{
           required_email_field: field,
           actor: actor
         } = context do
      custom_field_values =
        all_required_custom_fields_with_defaults(context)
        |> Enum.map(fn cfv ->
          if cfv["custom_field_id"] == field.id do
            %{cfv | "value" => %{"_union_type" => "email", "_union_value" => nil}}
          else
            cfv
          end
        end)

      attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)

      assert {:error, %Ash.Error.Invalid{errors: errors}} =
               Membership.create_member(attrs, actor: actor)

      assert error_message(errors, :custom_field_values) =~ "Required custom fields missing"
      assert error_message(errors, :custom_field_values) =~ field.name
    end

    test "fails when required email custom field has empty string value",
         %{
           required_email_field: field,
           actor: actor
         } = context do
      custom_field_values =
        all_required_custom_fields_with_defaults(context)
        |> Enum.map(fn cfv ->
          if cfv["custom_field_id"] == field.id do
            %{cfv | "value" => %{"_union_type" => "email", "_union_value" => ""}}
          else
            cfv
          end
        end)

      attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)

      assert {:error, %Ash.Error.Invalid{errors: errors}} =
               Membership.create_member(attrs, actor: actor)

      assert error_message(errors, :custom_field_values) =~ "Required custom fields missing"
      assert error_message(errors, :custom_field_values) =~ field.name
    end

    test "succeeds when required email custom field has valid email value",
         %{
           required_email_field: _field,
           actor: actor
         } = context do
      custom_field_values = all_required_custom_fields_with_defaults(context)

      attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)

      assert {:ok, _member} = Membership.create_member(attrs, actor: actor)
    end

    test "succeeds when multiple required custom fields are provided",
         %{
           required_string_field: string_field,
           required_integer_field: integer_field,
           required_boolean_field: boolean_field,
           actor: actor
         } = context do
      custom_field_values =
        all_required_custom_fields_with_defaults(context)
        |> Enum.map(fn cfv ->
          cond do
            cfv["custom_field_id"] == string_field.id ->
              %{cfv | "value" => %{"_union_type" => "string", "_union_value" => "test"}}

            cfv["custom_field_id"] == integer_field.id ->
              %{cfv | "value" => %{"_union_type" => "integer", "_union_value" => 42}}

            cfv["custom_field_id"] == boolean_field.id ->
              %{cfv | "value" => %{"_union_type" => "boolean", "_union_value" => true}}

            true ->
              cfv
          end
        end)

      attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)

      assert {:ok, _member} = Membership.create_member(attrs, actor: actor)
    end

    test "fails when one of multiple required custom fields is missing",
         %{
           required_string_field: string_field,
           required_integer_field: integer_field,
           actor: actor
         } = context do
      # Provide only string field, missing integer, boolean, and date
      custom_field_values =
        all_required_custom_fields_with_defaults(context)
        |> Enum.filter(fn cfv ->
          cfv["custom_field_id"] == string_field.id
        end)
        |> Enum.map(fn cfv ->
          %{cfv | "value" => %{"_union_type" => "string", "_union_value" => "test"}}
        end)

      attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)

      assert {:error, %Ash.Error.Invalid{errors: errors}} =
               Membership.create_member(attrs, actor: actor)

      assert error_message(errors, :custom_field_values) =~ "Required custom fields missing"
      assert error_message(errors, :custom_field_values) =~ integer_field.name
    end

    test "succeeds when optional custom field is missing", %{actor: actor} = context do
      # Provide all required fields, but no optional field
      custom_field_values = all_required_custom_fields_with_defaults(context)

      attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)

      assert {:ok, _member} = Membership.create_member(attrs, actor: actor)
    end

    test "succeeds when optional custom field has nil value",
         %{optional_field: field, actor: actor} = context do
      # Provide all required fields plus optional field with nil
      custom_field_values =
        all_required_custom_fields_with_defaults(context) ++
          [
            %{
              "custom_field_id" => field.id,
              "value" => %{"_union_type" => "string", "_union_value" => nil}
            }
          ]

      attrs = Map.put(@valid_attrs, :custom_field_values, custom_field_values)

      assert {:ok, _member} = Membership.create_member(attrs, actor: actor)
    end
  end

  describe "update_member with required custom fields" do
    test "fails when removing a required custom field value",
         %{
           required_string_field: field,
           actor: actor
         } = context do
      # Create member with all required custom fields
      custom_field_values = all_required_custom_fields_with_defaults(context)

      {:ok, member} =
        Membership.create_member(
          %{
            first_name: "John",
            last_name: "Doe",
            email: "john@example.com",
            custom_field_values: custom_field_values
          },
          actor: actor
        )

      # Try to update without the required custom field
      assert {:error, %Ash.Error.Invalid{errors: errors}} =
               Membership.update_member(member, %{custom_field_values: []}, actor: actor)

      assert error_message(errors, :custom_field_values) =~ "Required custom fields missing"
      assert error_message(errors, :custom_field_values) =~ field.name
    end

    test "fails when setting required custom field value to empty",
         %{
           required_string_field: field,
           actor: actor
         } = context do
      # Create member with all required custom fields
      custom_field_values = all_required_custom_fields_with_defaults(context)

      {:ok, member} =
        Membership.create_member(
          %{
            first_name: "John",
            last_name: "Doe",
            email: "john@example.com",
            custom_field_values: custom_field_values
          },
          actor: actor
        )

      # Try to update with empty value for the string field
      updated_custom_field_values =
        all_required_custom_fields_with_defaults(context)
        |> Enum.map(fn cfv ->
          if cfv["custom_field_id"] == field.id do
            %{cfv | "value" => %{"_union_type" => "string", "_union_value" => ""}}
          else
            cfv
          end
        end)

      assert {:error, %Ash.Error.Invalid{errors: errors}} =
               Membership.update_member(
                 member,
                 %{
                   custom_field_values: updated_custom_field_values
                 },
                 actor: actor
               )

      assert error_message(errors, :custom_field_values) =~ "Required custom fields missing"
      assert error_message(errors, :custom_field_values) =~ field.name
    end

    test "succeeds when updating required custom field to valid value",
         %{
           required_string_field: field,
           actor: actor
         } = context do
      # Create member with all required custom fields
      custom_field_values = all_required_custom_fields_with_defaults(context)

      {:ok, member} =
        Membership.create_member(
          %{
            first_name: "John",
            last_name: "Doe",
            email: "john@example.com",
            custom_field_values: custom_field_values
          },
          actor: actor
        )

      # Load existing custom field values to get their IDs
      {:ok, member_with_cfvs} = Ash.load(member, :custom_field_values, actor: actor)

      # Update with new valid value for the string field, using existing IDs
      updated_custom_field_values =
        member_with_cfvs.custom_field_values
        |> Enum.map(fn cfv ->
          if cfv.custom_field_id == field.id do
            %{
              "id" => cfv.id,
              "custom_field_id" => cfv.custom_field_id,
              "value" => %{"_union_type" => "string", "_union_value" => "new value"}
            }
          else
            # Keep other fields as they are
            value_type = Atom.to_string(cfv.value.type)
            actual_value = cfv.value.value

            %{
              "id" => cfv.id,
              "custom_field_id" => cfv.custom_field_id,
              "value" => %{"_union_type" => value_type, "_union_value" => actual_value}
            }
          end
        end)

      assert {:ok, _updated_member} =
               Membership.update_member(
                 member,
                 %{
                   custom_field_values: updated_custom_field_values
                 },
                 actor: actor
               )
    end
  end

  # Helper function for error evaluation
  defp error_message(errors, field) do
    errors
    |> Enum.filter(fn err -> Map.get(err, :field) == field end)
    |> Enum.map_join(" ", &Map.get(&1, :message, ""))
  end
end
