defmodule Mv.Membership.CustomFieldValidationTest do
  @moduledoc """
  Tests for CustomField validation constraints.

  Tests cover:
  - Name length validation (max 100 characters)
  - Name trimming
  - Description length validation (max 500 characters)
  - Description trimming
  - Required vs optional fields
  - Value type immutability (cannot be changed after creation)
  """
  use Mv.DataCase, async: true

  alias Mv.Membership.CustomField

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

  describe "name validation" do
    test "accepts name with exactly 100 characters", %{actor: actor} do
      name = String.duplicate("a", 100)

      assert {:ok, custom_field} =
               CustomField
               |> Ash.Changeset.for_create(:create, %{
                 name: name,
                 value_type: :string
               })
               |> Ash.create(actor: actor)

      assert custom_field.name == name
      assert String.length(custom_field.name) == 100
    end

    test "rejects name with 101 characters", %{actor: actor} do
      name = String.duplicate("a", 101)

      assert {:error, changeset} =
               CustomField
               |> Ash.Changeset.for_create(:create, %{
                 name: name,
                 value_type: :string
               })
               |> Ash.create(actor: actor)

      assert [%{field: :name, message: message}] = changeset.errors
      assert message =~ "max" or message =~ "length" or message =~ "100"
    end

    test "trims whitespace from name", %{actor: actor} do
      assert {:ok, custom_field} =
               CustomField
               |> Ash.Changeset.for_create(:create, %{
                 name: "  test_field  ",
                 value_type: :string
               })
               |> Ash.create(actor: actor)

      assert custom_field.name == "test_field"
    end

    test "rejects empty name", %{actor: actor} do
      assert {:error, changeset} =
               CustomField
               |> Ash.Changeset.for_create(:create, %{
                 name: "",
                 value_type: :string
               })
               |> Ash.create(actor: actor)

      assert Enum.any?(changeset.errors, fn error -> error.field == :name end)
    end

    test "rejects nil name", %{actor: actor} do
      assert {:error, changeset} =
               CustomField
               |> Ash.Changeset.for_create(:create, %{
                 value_type: :string
               })
               |> Ash.create(actor: actor)

      assert Enum.any?(changeset.errors, fn error -> error.field == :name end)
    end
  end

  describe "description validation" do
    test "accepts description with exactly 500 characters", %{actor: actor} do
      description = String.duplicate("a", 500)

      assert {:ok, custom_field} =
               CustomField
               |> Ash.Changeset.for_create(:create, %{
                 name: "test_field",
                 value_type: :string,
                 description: description
               })
               |> Ash.create(actor: actor)

      assert custom_field.description == description
      assert String.length(custom_field.description) == 500
    end

    test "rejects description with 501 characters", %{actor: actor} do
      description = String.duplicate("a", 501)

      assert {:error, changeset} =
               CustomField
               |> Ash.Changeset.for_create(:create, %{
                 name: "test_field",
                 value_type: :string,
                 description: description
               })
               |> Ash.create(actor: actor)

      assert [%{field: :description, message: message}] = changeset.errors
      assert message =~ "max" or message =~ "length" or message =~ "500"
    end

    test "trims whitespace from description", %{actor: actor} do
      assert {:ok, custom_field} =
               CustomField
               |> Ash.Changeset.for_create(:create, %{
                 name: "test_field",
                 value_type: :string,
                 description: "  A nice description  "
               })
               |> Ash.create(actor: actor)

      assert custom_field.description == "A nice description"
    end

    test "accepts nil description (optional field)", %{actor: actor} do
      assert {:ok, custom_field} =
               CustomField
               |> Ash.Changeset.for_create(:create, %{
                 name: "test_field",
                 value_type: :string
               })
               |> Ash.create(actor: actor)

      assert custom_field.description == nil
    end

    test "accepts empty description after trimming", %{actor: actor} do
      assert {:ok, custom_field} =
               CustomField
               |> Ash.Changeset.for_create(:create, %{
                 name: "test_field",
                 value_type: :string,
                 description: "   "
               })
               |> Ash.create(actor: actor)

      # After trimming whitespace, becomes nil (empty strings are converted to nil)
      assert custom_field.description == nil
    end
  end

  describe "name uniqueness" do
    test "rejects duplicate names", %{actor: actor} do
      assert {:ok, _} =
               CustomField
               |> Ash.Changeset.for_create(:create, %{
                 name: "unique_field",
                 value_type: :string
               })
               |> Ash.create(actor: actor)

      assert {:error, changeset} =
               CustomField
               |> Ash.Changeset.for_create(:create, %{
                 name: "unique_field",
                 value_type: :integer
               })
               |> Ash.create(actor: actor)

      assert Enum.any?(changeset.errors, fn error -> error.field == :name end)
    end
  end

  describe "value_type validation" do
    test "accepts all valid value types", %{actor: actor} do
      for value_type <- [:string, :integer, :boolean, :date, :email] do
        assert {:ok, custom_field} =
                 CustomField
                 |> Ash.Changeset.for_create(:create, %{
                   name: "field_#{value_type}",
                   value_type: value_type
                 })
                 |> Ash.create(actor: actor)

        assert custom_field.value_type == value_type
      end
    end

    test "rejects invalid value type", %{actor: actor} do
      assert {:error, changeset} =
               CustomField
               |> Ash.Changeset.for_create(:create, %{
                 name: "invalid_field",
                 value_type: :invalid_type
               })
               |> Ash.create(actor: actor)

      assert [%{field: :value_type}] = changeset.errors
    end
  end

  describe "value_type immutability" do
    test "rejects attempt to change value_type after creation", %{actor: actor} do
      # Create custom field with value_type :string
      {:ok, custom_field} =
        CustomField
        |> Ash.Changeset.for_create(:create, %{
          name: "test_field",
          value_type: :string
        })
        |> Ash.create(actor: actor)

      original_value_type = custom_field.value_type
      assert original_value_type == :string

      # Attempt to update value_type to :integer
      assert {:error, %Ash.Error.Invalid{} = error} =
               custom_field
               |> Ash.Changeset.for_update(:update, %{
                 value_type: :integer
               })
               |> Ash.update(actor: actor)

      # Verify error message contains expected text
      error_message = Exception.message(error)
      assert error_message =~ "cannot be changed" or error_message =~ "value_type"

      # Reload and verify value_type remained unchanged
      reloaded = Ash.get!(CustomField, custom_field.id, actor: actor)
      assert reloaded.value_type == original_value_type
      assert reloaded.value_type == :string
    end

    test "allows updating other fields while value_type remains unchanged", %{actor: actor} do
      # Create custom field with value_type :string
      {:ok, custom_field} =
        CustomField
        |> Ash.Changeset.for_create(:create, %{
          name: "test_field",
          value_type: :string,
          description: "Original description"
        })
        |> Ash.create(actor: actor)

      original_value_type = custom_field.value_type
      assert original_value_type == :string

      # Update other fields (name, description) without touching value_type
      {:ok, updated_custom_field} =
        custom_field
        |> Ash.Changeset.for_update(:update, %{
          name: "updated_name",
          description: "Updated description"
        })
        |> Ash.update(actor: actor)

      # Verify value_type remained unchanged
      assert updated_custom_field.value_type == original_value_type
      assert updated_custom_field.value_type == :string
      # Verify other fields were updated
      assert updated_custom_field.name == "updated_name"
      assert updated_custom_field.description == "Updated description"
    end

    test "rejects value_type change even when other fields are updated", %{actor: actor} do
      # Create custom field with value_type :boolean
      {:ok, custom_field} =
        CustomField
        |> Ash.Changeset.for_create(:create, %{
          name: "test_field",
          value_type: :boolean
        })
        |> Ash.create(actor: actor)

      original_value_type = custom_field.value_type
      assert original_value_type == :boolean

      # Attempt to update both name and value_type
      assert {:error, %Ash.Error.Invalid{} = error} =
               custom_field
               |> Ash.Changeset.for_update(:update, %{
                 name: "updated_name",
                 value_type: :date
               })
               |> Ash.update(actor: actor)

      # Verify error message
      error_message = Exception.message(error)
      assert error_message =~ "cannot be changed" or error_message =~ "value_type"

      # Reload and verify value_type remained unchanged, but name was not updated either
      reloaded = Ash.get!(CustomField, custom_field.id, actor: actor)
      assert reloaded.value_type == original_value_type
      assert reloaded.value_type == :boolean
      assert reloaded.name == "test_field"
    end
  end
end
