defmodule Mv.MembershipFees.Changes.ValidateSameIntervalTest do
  @moduledoc """
  Tests for ValidateSameInterval change module.
  """
  use Mv.DataCase, async: true

  alias Mv.MembershipFees.MembershipFeeType
  alias Mv.MembershipFees.Changes.ValidateSameInterval

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

  # Helper to create a membership fee type
  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 member
  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

  describe "validate_interval_match/1" do
    test "allows change to type with same interval", %{actor: actor} do
      yearly_type1 = create_fee_type(%{interval: :yearly, name: "Yearly Type 1"}, actor)
      yearly_type2 = create_fee_type(%{interval: :yearly, name: "Yearly Type 2"}, actor)

      member = create_member(%{membership_fee_type_id: yearly_type1.id}, actor)

      changeset =
        member
        |> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: yearly_type2.id},
          actor: actor
        )
        |> ValidateSameInterval.change(%{}, %{actor: actor})

      assert changeset.valid?
    end

    test "prevents change to type with different interval", %{actor: actor} do
      yearly_type = create_fee_type(%{interval: :yearly}, actor)
      monthly_type = create_fee_type(%{interval: :monthly}, actor)

      member = create_member(%{membership_fee_type_id: yearly_type.id}, actor)

      changeset =
        member
        |> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: monthly_type.id},
          actor: actor
        )
        |> ValidateSameInterval.change(%{}, %{actor: actor})

      refute changeset.valid?
      assert %{errors: errors} = changeset

      assert Enum.any?(errors, fn error ->
               error.field == :membership_fee_type_id and
                 error.message =~ "yearly" and
                 error.message =~ "monthly"
             end)
    end

    test "allows first assignment of membership fee type", %{actor: actor} do
      yearly_type = create_fee_type(%{interval: :yearly}, actor)
      # No fee type assigned
      member = create_member(%{}, actor)

      changeset =
        member
        |> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: yearly_type.id},
          actor: actor
        )
        |> ValidateSameInterval.change(%{}, %{actor: actor})

      assert changeset.valid?
    end

    test "prevents removal of membership fee type", %{actor: actor} do
      yearly_type = create_fee_type(%{interval: :yearly}, actor)
      member = create_member(%{membership_fee_type_id: yearly_type.id}, actor)

      changeset =
        member
        |> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: nil}, actor: actor)
        |> ValidateSameInterval.change(%{}, %{actor: actor})

      refute changeset.valid?
      assert %{errors: errors} = changeset

      assert Enum.any?(errors, fn error ->
               error.field == :membership_fee_type_id and
                 error.message =~ "Cannot remove membership fee type"
             end)
    end

    test "does nothing when membership_fee_type_id is not changed", %{actor: actor} do
      yearly_type = create_fee_type(%{interval: :yearly}, actor)
      member = create_member(%{membership_fee_type_id: yearly_type.id}, actor)

      changeset =
        member
        |> Ash.Changeset.for_update(:update_member, %{first_name: "New Name"}, actor: actor)
        |> ValidateSameInterval.change(%{}, %{actor: actor})

      assert changeset.valid?
    end

    test "error message is clear and helpful", %{actor: actor} do
      yearly_type = create_fee_type(%{interval: :yearly}, actor)
      quarterly_type = create_fee_type(%{interval: :quarterly}, actor)

      member = create_member(%{membership_fee_type_id: yearly_type.id}, actor)

      changeset =
        member
        |> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: quarterly_type.id},
          actor: actor
        )
        |> ValidateSameInterval.change(%{}, %{actor: actor})

      error = Enum.find(changeset.errors, &(&1.field == :membership_fee_type_id))
      assert error.message =~ "yearly"
      assert error.message =~ "quarterly"
      assert error.message =~ "same-interval"
    end

    test "handles all interval types correctly", %{actor: actor} do
      intervals = [:monthly, :quarterly, :half_yearly, :yearly]

      for interval1 <- intervals,
          interval2 <- intervals,
          interval1 != interval2 do
        type1 =
          create_fee_type(
            %{
              interval: interval1,
              name: "Type #{interval1} #{System.unique_integer([:positive])}"
            },
            actor
          )

        type2 =
          create_fee_type(
            %{
              interval: interval2,
              name: "Type #{interval2} #{System.unique_integer([:positive])}"
            },
            actor
          )

        member = create_member(%{membership_fee_type_id: type1.id}, actor)

        changeset =
          member
          |> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: type2.id},
            actor: actor
          )
          |> ValidateSameInterval.change(%{}, %{actor: actor})

        refute changeset.valid?,
               "Should prevent change from #{interval1} to #{interval2}"
      end
    end
  end

  describe "integration with update_member action" do
    test "validation works when updating member via update_member action", %{actor: actor} do
      yearly_type = create_fee_type(%{interval: :yearly}, actor)
      monthly_type = create_fee_type(%{interval: :monthly}, actor)

      member = create_member(%{membership_fee_type_id: yearly_type.id}, actor)

      # Try to update member with different interval type
      assert {:error, %Ash.Error.Invalid{} = error} =
               Mv.Membership.update_member(member, %{membership_fee_type_id: monthly_type.id},
                 actor: actor
               )

      # Check that error is about interval mismatch
      error_message = extract_error_message(error)
      assert error_message =~ "yearly"
      assert error_message =~ "monthly"
      assert error_message =~ "same-interval"
    end

    test "allows update when interval matches", %{actor: actor} do
      yearly_type1 = create_fee_type(%{interval: :yearly, name: "Yearly Type 1"}, actor)
      yearly_type2 = create_fee_type(%{interval: :yearly, name: "Yearly Type 2"}, actor)

      member = create_member(%{membership_fee_type_id: yearly_type1.id}, actor)

      # Update member with same-interval type
      assert {:ok, updated_member} =
               Mv.Membership.update_member(member, %{membership_fee_type_id: yearly_type2.id},
                 actor: actor
               )

      assert updated_member.membership_fee_type_id == yearly_type2.id
    end

    defp extract_error_message(%Ash.Error.Invalid{errors: errors}) do
      errors
      |> Enum.filter(&(&1.field == :membership_fee_type_id))
      |> Enum.map_join(" ", & &1.message)
    end
  end
end
