defmodule Mv.MembershipFees.MembershipFeeTypeTest do
  @moduledoc """
  Tests for MembershipFeeType business rules only.

  We test: required fields, allowed interval values, uniqueness, amount constraints,
  interval immutability, and referential integrity (cannot delete when in use).
  We do not test: standard CRUD (create/update/delete when no constraints apply).
  """
  use Mv.DataCase, async: true

  alias Mv.MembershipFees.MembershipFeeType

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

  describe "create MembershipFeeType - business rules" do
    test "requires name", %{actor: actor} do
      attrs = %{
        amount: Decimal.new("100.00"),
        interval: :yearly
      }

      assert {:error, error} = Ash.create(MembershipFeeType, attrs, actor: actor)
      assert error_on_field?(error, :name)
    end

    test "requires amount", %{actor: actor} do
      attrs = %{
        name: "Test Fee",
        interval: :yearly
      }

      assert {:error, error} = Ash.create(MembershipFeeType, attrs, actor: actor)
      assert error_on_field?(error, :amount)
    end

    test "requires interval", %{actor: actor} do
      attrs = %{
        name: "Test Fee",
        amount: Decimal.new("100.00")
      }

      assert {:error, error} = Ash.create(MembershipFeeType, attrs, actor: actor)
      assert error_on_field?(error, :interval)
    end

    test "accepts valid interval values (monthly, quarterly, half_yearly, yearly)", %{
      actor: actor
    } do
      for {interval, name} <- [
            monthly: "Monthly",
            quarterly: "Quarterly",
            half_yearly: "Half Yearly",
            yearly: "Yearly"
          ] do
        attrs = %{
          name: "#{name} #{System.unique_integer([:positive])}",
          amount: Decimal.new("10.00"),
          interval: interval
        }

        assert {:ok, fee_type} = Ash.create(MembershipFeeType, attrs, actor: actor)
        assert fee_type.interval == interval
      end
    end

    test "rejects invalid interval values", %{actor: actor} do
      attrs = %{name: "Invalid", amount: Decimal.new("100.00"), interval: :weekly}
      assert {:error, error} = Ash.create(MembershipFeeType, attrs, actor: actor)
      assert error_on_field?(error, :interval)
    end

    test "name must be unique", %{actor: actor} do
      attrs = %{name: "Unique Name", amount: Decimal.new("100.00"), interval: :yearly}

      assert {:ok, _} = Ash.create(MembershipFeeType, attrs, actor: actor)
      assert {:error, error} = Ash.create(MembershipFeeType, attrs, actor: actor)

      # Check for uniqueness error
      assert error_on_field?(error, :name)
    end

    test "rejects negative amount", %{actor: actor} do
      attrs = %{name: "Negative Test", amount: Decimal.new("-10.00"), interval: :yearly}
      assert {:error, error} = Ash.create(MembershipFeeType, attrs, actor: actor)
      assert error_on_field?(error, :amount)
    end

    test "accepts zero amount", %{actor: actor} do
      attrs = %{name: "Zero Amount", amount: Decimal.new("0.00"), interval: :yearly}
      assert {:ok, fee_type} = Ash.create(MembershipFeeType, attrs, actor: actor)
      assert Decimal.equal?(fee_type.amount, Decimal.new("0.00"))
    end

    test "amount respects scale of 2 decimal places", %{actor: actor} do
      attrs = %{name: "Scale Test", amount: Decimal.new("100.50"), interval: :yearly}
      assert {:ok, fee_type} = Ash.create(MembershipFeeType, attrs, actor: actor)
      assert Decimal.equal?(fee_type.amount, Decimal.new("100.50"))
    end
  end

  describe "update MembershipFeeType - business rules" do
    setup %{actor: actor} do
      {:ok, fee_type} =
        Ash.create(
          MembershipFeeType,
          %{
            name: "Original Name #{System.unique_integer([:positive])}",
            amount: Decimal.new("100.00"),
            interval: :yearly,
            description: "Original description"
          },
          actor: actor
        )

      %{fee_type: fee_type}
    end

    test "interval immutability: update fails when interval is changed", %{
      actor: actor,
      fee_type: fee_type
    } do
      # Currently, interval is not in the accept list, so it's rejected as "NoSuchInput"
      # After implementing validation, it should return a validation error
      assert {:error, error} = Ash.update(fee_type, %{interval: :monthly}, actor: actor)
      # For now, check that it's an error (either NoSuchInput or validation error)
      assert %Ash.Error.Invalid{} = error
    end
  end

  describe "delete MembershipFeeType - business rules (referential integrity)" do
    setup %{actor: actor} do
      {:ok, fee_type} =
        Ash.create(
          MembershipFeeType,
          %{
            name: "Test Fee Type #{System.unique_integer([:positive])}",
            amount: Decimal.new("100.00"),
            interval: :yearly
          },
          actor: actor
        )

      %{fee_type: fee_type}
    end

    test "cannot delete when members are assigned", %{actor: actor, fee_type: fee_type} do
      alias Mv.Membership.Member

      # Create a member with this fee type
      {:ok, _member} =
        Ash.create(
          Member,
          %{
            first_name: "Test",
            last_name: "Member",
            email: "test.member.#{System.unique_integer([:positive])}@example.com",
            membership_fee_type_id: fee_type.id
          },
          actor: actor
        )

      assert {:error, error} = Ash.destroy(fee_type, actor: actor)
      # Check for either validation error message or DB constraint error
      error_message = extract_error_message(error)
      assert error_message =~ "member" or error_message =~ "referenced"
    end

    test "cannot delete when cycles exist", %{actor: actor, fee_type: fee_type} do
      alias Mv.MembershipFees.MembershipFeeCycle
      alias Mv.Membership.Member

      # Create a member with this fee type
      {:ok, member} =
        Ash.create(
          Member,
          %{
            first_name: "Test",
            last_name: "Member",
            email: "test.member.#{System.unique_integer([:positive])}@example.com",
            membership_fee_type_id: fee_type.id
          },
          actor: actor
        )

      # Create a cycle for this fee type
      {:ok, _cycle} =
        Ash.create(
          MembershipFeeCycle,
          %{
            cycle_start: ~D[2025-01-01],
            amount: Decimal.new("100.00"),
            member_id: member.id,
            membership_fee_type_id: fee_type.id
          },
          actor: actor
        )

      assert {:error, error} = Ash.destroy(fee_type, actor: actor)
      # Check for either validation error message or DB constraint error
      error_message = extract_error_message(error)
      assert error_message =~ "cycle" or error_message =~ "referenced"
    end

    test "cannot delete when used as default in settings", %{actor: actor, fee_type: fee_type} do
      # Set as default in settings
      {:ok, settings} = Mv.Membership.get_settings()

      settings
      |> Ash.Changeset.for_update(
        :update_membership_fee_settings,
        %{
          default_membership_fee_type_id: fee_type.id
        },
        actor: actor
      )
      |> Ash.update!(actor: actor)

      # Try to delete
      assert {:error, error} = Ash.destroy(fee_type, actor: actor)
      error_message = extract_error_message(error)
      assert error_message =~ "used as default in settings"
    end
  end

  # Helper to check if an error occurred on a specific field
  defp error_on_field?(%Ash.Error.Invalid{} = error, field) do
    Enum.any?(error.errors, fn e ->
      case e do
        %{field: ^field} -> true
        %{fields: fields} when is_list(fields) -> field in fields
        _ -> false
      end
    end)
  end

  defp error_on_field?(_, _), do: false

  # Helper to extract error message from various error types
  defp extract_error_message(%Ash.Error.Invalid{} = error) do
    Enum.map_join(error.errors, " ", fn
      %{message: message} -> message
      %{detail: detail} -> detail
      _ -> ""
    end)
  end

  defp extract_error_message(_), do: ""
end
