defmodule Mv.MembershipFees.MembershipFeeTypeIntegrationTest do
  @moduledoc """
  Integration tests for MembershipFeeType CRUD operations.
  """
  use Mv.DataCase, async: false

  alias Mv.MembershipFees.MembershipFeeType
  alias Mv.MembershipFees.MembershipFeeCycle
  alias Mv.Membership.Member

  require Ash.Query

  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

  describe "admin can create membership fee type" do
    test "creates type with all fields", %{actor: actor} do
      attrs = %{
        name: "Standard Membership",
        amount: Decimal.new("120.00"),
        interval: :yearly,
        description: "Standard yearly membership fee"
      }

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

      assert fee_type.name == "Standard Membership"
      assert Decimal.equal?(fee_type.amount, Decimal.new("120.00"))
      assert fee_type.interval == :yearly
      assert fee_type.description == "Standard yearly membership fee"
    end
  end

  describe "admin can update membership fee type" do
    setup %{actor: actor} do
      {:ok, fee_type} =
        Ash.create(
          MembershipFeeType,
          %{
            name: "Original Name",
            amount: Decimal.new("100.00"),
            interval: :yearly,
            description: "Original description"
          },
          actor: actor
        )

      %{fee_type: fee_type}
    end

    test "can update name", %{actor: actor, fee_type: fee_type} do
      assert {:ok, updated} = Ash.update(fee_type, %{name: "Updated Name"}, actor: actor)
      assert updated.name == "Updated Name"
    end

    test "can update amount", %{actor: actor, fee_type: fee_type} do
      assert {:ok, updated} = Ash.update(fee_type, %{amount: Decimal.new("150.00")}, actor: actor)
      assert Decimal.equal?(updated.amount, Decimal.new("150.00"))
    end

    test "can update description", %{actor: actor, fee_type: fee_type} do
      assert {:ok, updated} =
               Ash.update(fee_type, %{description: "Updated description"}, actor: actor)

      assert updated.description == "Updated description"
    end

    test "cannot update interval", %{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 "admin cannot delete membership fee type when in use" do
    test "cannot delete when members are assigned", %{actor: actor} do
      fee_type = create_fee_type(%{interval: :yearly}, actor)

      # 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)
      error_message = extract_error_message(error)
      assert error_message =~ "member(s) are assigned"
    end

    test "cannot delete when cycles exist", %{actor: actor} do
      fee_type = create_fee_type(%{interval: :yearly}, actor)

      # 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)
      error_message = extract_error_message(error)
      assert error_message =~ "cycle(s) reference"
    end

    test "cannot delete when used as default in settings", %{actor: actor} do
      fee_type = create_fee_type(%{interval: :yearly}, actor)

      # 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

  describe "settings integration" do
    test "default_membership_fee_type_id is used during member creation", %{actor: actor} do
      # Create a fee type
      fee_type = create_fee_type(%{interval: :yearly}, actor)

      # Set it 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)

      # Create a member without explicitly setting membership_fee_type_id
      # The Member resource automatically assigns the default_membership_fee_type_id
      # during creation via SetDefaultMembershipFeeType change.
      {:ok, member} =
        Ash.create(
          Member,
          %{
            first_name: "Test",
            last_name: "Member",
            email: "test.member.#{System.unique_integer([:positive])}@example.com"
          },
          actor: actor
        )

      # Verify that the default membership fee type was automatically assigned
      assert member.membership_fee_type_id == fee_type.id
    end

    test "include_joining_cycle is used during cycle generation", %{actor: actor} do
      # This test verifies that the include_joining_cycle setting affects
      # cycle generation. The actual cycle generation logic is tested in
      # CycleGeneratorTest, but this integration test ensures the setting
      # is properly used.

      fee_type = create_fee_type(%{interval: :yearly}, actor)

      # Set include_joining_cycle to false
      {:ok, settings} = Mv.Membership.get_settings()

      settings
      |> Ash.Changeset.for_update(:update_membership_fee_settings, %{
        include_joining_cycle: false
      })
      |> Ash.update!(actor: actor)

      # Create a member with join_date in the middle of a year
      {:ok, member} =
        Ash.create(
          Member,
          %{
            first_name: "Test",
            last_name: "Member",
            email: "test.member.#{System.unique_integer([:positive])}@example.com",
            join_date: ~D[2023-03-15],
            membership_fee_type_id: fee_type.id
          },
          actor: actor
        )

      # Verify that membership_fee_start_date was calculated correctly
      # (should be 2024-01-01, not 2023-01-01, because include_joining_cycle = false)
      assert member.membership_fee_start_date == ~D[2024-01-01]
    end
  end

  # 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
