defmodule Mv.MembershipFees.MemberCycleIntegrationTest do
  @moduledoc """
  Integration tests for membership fee cycle generation triggered by member actions.
  """
  use Mv.DataCase, async: false

  alias Mv.MembershipFees.MembershipFeeCycle
  alias Mv.MembershipFees.MembershipFeeType

  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

  # Helper to set up settings
  defp setup_settings(include_joining_cycle, actor) do
    {:ok, settings} = Mv.Membership.get_settings()

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

  # Helper to get cycles for a member
  defp get_member_cycles(member_id, actor) do
    MembershipFeeCycle
    |> Ash.Query.filter(member_id == ^member_id)
    |> Ash.Query.sort(cycle_start: :asc)
    |> Ash.read!(actor: actor)
  end

  describe "member creation triggers cycle generation" do
    test "creates cycles when member is created with fee type and join_date", %{actor: actor} do
      setup_settings(true, actor)
      fee_type = create_fee_type(%{interval: :yearly}, actor)

      {:ok, member} =
        Mv.Membership.create_member(
          %{
            first_name: "Test",
            last_name: "User",
            email: "test#{System.unique_integer([:positive])}@example.com",
            join_date: ~D[2023-03-15],
            membership_fee_type_id: fee_type.id
          },
          actor: actor
        )

      cycles = get_member_cycles(member.id, actor)

      # Should have cycles for 2023 and 2024 (and possibly current year)
      assert length(cycles) >= 2

      # Verify cycles have correct data
      Enum.each(cycles, fn cycle ->
        assert cycle.member_id == member.id
        assert cycle.membership_fee_type_id == fee_type.id
        assert Decimal.equal?(cycle.amount, fee_type.amount)
        assert cycle.status == :unpaid
      end)
    end

    test "does not create cycles when member has no fee type", %{actor: actor} do
      setup_settings(true, actor)

      {:ok, member} =
        Mv.Membership.create_member(
          %{
            first_name: "Test",
            last_name: "User",
            email: "test#{System.unique_integer([:positive])}@example.com",
            join_date: ~D[2023-03-15]
          },
          actor: actor
        )

      cycles = get_member_cycles(member.id, actor)

      assert cycles == []
    end

    test "does not create cycles when member has no join_date", %{actor: actor} do
      setup_settings(true, actor)
      fee_type = create_fee_type(%{interval: :yearly}, actor)

      {:ok, member} =
        Mv.Membership.create_member(
          %{
            first_name: "Test",
            last_name: "User",
            email: "test#{System.unique_integer([:positive])}@example.com",
            membership_fee_type_id: fee_type.id
          },
          actor: actor
        )

      cycles = get_member_cycles(member.id, actor)

      assert cycles == []
    end
  end

  describe "member update triggers cycle generation" do
    test "generates cycles when fee type is assigned to existing member", %{actor: actor} do
      setup_settings(true, actor)
      fee_type = create_fee_type(%{interval: :yearly}, actor)

      # Create member without fee type
      {:ok, member} =
        Mv.Membership.create_member(
          %{
            first_name: "Test",
            last_name: "User",
            email: "test#{System.unique_integer([:positive])}@example.com",
            join_date: ~D[2023-03-15]
          },
          actor: actor
        )

      # Verify no cycles yet
      assert get_member_cycles(member.id, actor) == []

      # Update to assign fee type
      {:ok, member} =
        Mv.Membership.update_member(member, %{membership_fee_type_id: fee_type.id}, actor: actor)

      cycles = get_member_cycles(member.id, actor)

      # Should have generated cycles
      assert length(cycles) >= 2
    end
  end

  describe "concurrent cycle generation" do
    test "handles multiple members being created concurrently", %{actor: actor} do
      setup_settings(true, actor)
      fee_type = create_fee_type(%{interval: :yearly}, actor)

      # Create multiple members concurrently
      tasks =
        Enum.map(1..5, fn i ->
          Task.async(fn ->
            {:ok, member} =
              Mv.Membership.create_member(
                %{
                  first_name: "Test#{i}",
                  last_name: "User#{i}",
                  email: "test#{System.unique_integer([:positive])}@example.com",
                  join_date: ~D[2023-03-15],
                  membership_fee_type_id: fee_type.id
                },
                actor: actor
              )

            member
          end)
        end)

      members = Enum.map(tasks, &Task.await/1)

      # Each member should have cycles
      Enum.each(members, fn member ->
        cycles = get_member_cycles(member.id, actor)
        assert length(cycles) >= 2, "Member #{member.id} should have at least 2 cycles"
      end)
    end
  end

  describe "idempotent cycle generation" do
    test "running generation multiple times does not create duplicate cycles", %{actor: actor} do
      setup_settings(true, actor)
      fee_type = create_fee_type(%{interval: :yearly}, actor)

      {:ok, member} =
        Mv.Membership.create_member(
          %{
            first_name: "Test",
            last_name: "User",
            email: "test#{System.unique_integer([:positive])}@example.com",
            join_date: ~D[2023-03-15],
            membership_fee_type_id: fee_type.id
          },
          actor: actor
        )

      initial_cycles = get_member_cycles(member.id, actor)
      initial_count = length(initial_cycles)

      # Use a fixed "today" date to avoid date dependency
      # Use a date far enough in the future to ensure all cycles are generated
      today = ~D[2025-12-31]

      # Manually trigger generation again with fixed "today" date
      {:ok, _, _} =
        Mv.MembershipFees.CycleGenerator.generate_cycles_for_member(member.id, today: today)

      final_cycles = get_member_cycles(member.id, actor)
      final_count = length(final_cycles)

      # Should have same number of cycles (idempotent)
      assert final_count == initial_count
    end
  end
end
