defmodule Mv.MembershipFees.Changes.SetMembershipFeeStartDateTest do
  @moduledoc """
  Tests for the SetMembershipFeeStartDate change module.
  """
  use Mv.DataCase, async: false

  alias Mv.MembershipFees.Changes.SetMembershipFeeStartDate

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

  # Helper to set up settings with specific include_joining_cycle value
  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

  describe "calculate_start_date/3" do
    test "include_joining_cycle = true: start date is first day of joining cycle (yearly)" do
      result = SetMembershipFeeStartDate.calculate_start_date(~D[2024-03-15], :yearly, true)
      assert result == ~D[2024-01-01]
    end

    test "include_joining_cycle = false: start date is first day of next cycle (yearly)" do
      result = SetMembershipFeeStartDate.calculate_start_date(~D[2024-03-15], :yearly, false)
      assert result == ~D[2025-01-01]
    end

    test "include_joining_cycle = true: start date is first day of joining cycle (quarterly)" do
      # Q1: Jan-Mar, Q2: Apr-Jun, Q3: Jul-Sep, Q4: Oct-Dec
      # March is in Q1
      result = SetMembershipFeeStartDate.calculate_start_date(~D[2024-03-15], :quarterly, true)
      assert result == ~D[2024-01-01]

      # May is in Q2
      result = SetMembershipFeeStartDate.calculate_start_date(~D[2024-05-15], :quarterly, true)
      assert result == ~D[2024-04-01]

      # August is in Q3
      result = SetMembershipFeeStartDate.calculate_start_date(~D[2024-08-15], :quarterly, true)
      assert result == ~D[2024-07-01]

      # November is in Q4
      result = SetMembershipFeeStartDate.calculate_start_date(~D[2024-11-15], :quarterly, true)
      assert result == ~D[2024-10-01]
    end

    test "include_joining_cycle = false: start date is first day of next cycle (quarterly)" do
      # March is in Q1, next is Q2
      result = SetMembershipFeeStartDate.calculate_start_date(~D[2024-03-15], :quarterly, false)
      assert result == ~D[2024-04-01]

      # June is in Q2, next is Q3
      result = SetMembershipFeeStartDate.calculate_start_date(~D[2024-06-15], :quarterly, false)
      assert result == ~D[2024-07-01]

      # September is in Q3, next is Q4
      result = SetMembershipFeeStartDate.calculate_start_date(~D[2024-09-15], :quarterly, false)
      assert result == ~D[2024-10-01]

      # December is in Q4, next is Q1 of next year
      result = SetMembershipFeeStartDate.calculate_start_date(~D[2024-12-15], :quarterly, false)
      assert result == ~D[2025-01-01]
    end

    test "include_joining_cycle = true: start date is first day of joining cycle (half_yearly)" do
      # H1: Jan-Jun, H2: Jul-Dec
      # March is in H1
      result = SetMembershipFeeStartDate.calculate_start_date(~D[2024-03-15], :half_yearly, true)
      assert result == ~D[2024-01-01]

      # September is in H2
      result = SetMembershipFeeStartDate.calculate_start_date(~D[2024-09-15], :half_yearly, true)
      assert result == ~D[2024-07-01]
    end

    test "include_joining_cycle = false: start date is first day of next cycle (half_yearly)" do
      # March is in H1, next is H2
      result = SetMembershipFeeStartDate.calculate_start_date(~D[2024-03-15], :half_yearly, false)
      assert result == ~D[2024-07-01]

      # September is in H2, next is H1 of next year
      result = SetMembershipFeeStartDate.calculate_start_date(~D[2024-09-15], :half_yearly, false)
      assert result == ~D[2025-01-01]
    end

    test "include_joining_cycle = true: start date is first day of joining cycle (monthly)" do
      result = SetMembershipFeeStartDate.calculate_start_date(~D[2024-03-15], :monthly, true)
      assert result == ~D[2024-03-01]
    end

    test "include_joining_cycle = false: start date is first day of next cycle (monthly)" do
      result = SetMembershipFeeStartDate.calculate_start_date(~D[2024-03-15], :monthly, false)
      assert result == ~D[2024-04-01]

      # December goes to next year
      result = SetMembershipFeeStartDate.calculate_start_date(~D[2024-12-15], :monthly, false)
      assert result == ~D[2025-01-01]
    end

    test "joining on first day of cycle with include_joining_cycle = true" do
      # When joining exactly on cycle start, should return that date
      result = SetMembershipFeeStartDate.calculate_start_date(~D[2024-01-01], :yearly, true)
      assert result == ~D[2024-01-01]

      result = SetMembershipFeeStartDate.calculate_start_date(~D[2024-04-01], :quarterly, true)
      assert result == ~D[2024-04-01]
    end

    test "joining on first day of cycle with include_joining_cycle = false" do
      # When joining exactly on cycle start and include=false, should return next cycle
      result = SetMembershipFeeStartDate.calculate_start_date(~D[2024-01-01], :yearly, false)
      assert result == ~D[2025-01-01]

      result = SetMembershipFeeStartDate.calculate_start_date(~D[2024-04-01], :quarterly, false)
      assert result == ~D[2024-07-01]
    end

    test "joining on last day of cycle" do
      # Joining on Dec 31 with yearly cycle
      result = SetMembershipFeeStartDate.calculate_start_date(~D[2024-12-31], :yearly, true)
      assert result == ~D[2024-01-01]

      result = SetMembershipFeeStartDate.calculate_start_date(~D[2024-12-31], :yearly, false)
      assert result == ~D[2025-01-01]
    end
  end

  describe "change/3 integration" do
    test "sets membership_fee_start_date automatically on member creation", %{actor: actor} do
      setup_settings(true, actor)

      # Create a fee type
      fee_type =
        Mv.MembershipFees.MembershipFeeType
        |> Ash.Changeset.for_create(:create, %{
          name: "Test Type #{System.unique_integer([:positive])}",
          amount: Decimal.new("50.00"),
          interval: :yearly
        })
        |> Ash.create!(actor: actor)

      # Create member with join_date and fee type but no explicit start date
      {:ok, member} =
        Mv.Membership.create_member(
          %{
            first_name: "Test",
            last_name: "User",
            email: "test#{System.unique_integer([:positive])}@example.com",
            join_date: ~D[2024-03-15],
            membership_fee_type_id: fee_type.id
          },
          actor: actor
        )

      # Should have auto-calculated start date (2024-01-01 for yearly with include_joining_cycle=true)
      assert member.membership_fee_start_date == ~D[2024-01-01]
    end

    test "does not override manually set membership_fee_start_date", %{actor: actor} do
      setup_settings(true, actor)

      # Create a fee type
      fee_type =
        Mv.MembershipFees.MembershipFeeType
        |> Ash.Changeset.for_create(:create, %{
          name: "Test Type #{System.unique_integer([:positive])}",
          amount: Decimal.new("50.00"),
          interval: :yearly
        })
        |> Ash.create!(actor: actor)

      # Create member with explicit start date
      manual_start_date = ~D[2024-07-01]

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

      # Should keep the manually set date
      assert member.membership_fee_start_date == manual_start_date
    end

    test "respects include_joining_cycle = false setting", %{actor: actor} do
      setup_settings(false, actor)

      # Create a fee type
      fee_type =
        Mv.MembershipFees.MembershipFeeType
        |> Ash.Changeset.for_create(:create, %{
          name: "Test Type #{System.unique_integer([:positive])}",
          amount: Decimal.new("50.00"),
          interval: :yearly
        })
        |> Ash.create!(actor: actor)

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

      # Should have next cycle start date (2025-01-01 for yearly with include_joining_cycle=false)
      assert member.membership_fee_start_date == ~D[2025-01-01]
    end

    test "does not set start date without join_date", %{actor: actor} do
      setup_settings(true, actor)

      # Create a fee type
      fee_type =
        Mv.MembershipFees.MembershipFeeType
        |> Ash.Changeset.for_create(:create, %{
          name: "Test Type #{System.unique_integer([:positive])}",
          amount: Decimal.new("50.00"),
          interval: :yearly
        })
        |> Ash.create!(actor: actor)

      # Create member without join_date
      {: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
            # No join_date
          },
          actor: actor
        )

      # Should not have auto-calculated start date
      assert is_nil(member.membership_fee_start_date)
    end

    test "does not set start date without membership_fee_type_id", %{actor: actor} do
      setup_settings(true, 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[2024-03-15]
            # No membership_fee_type_id
          },
          actor: actor
        )

      # Should not have auto-calculated start date
      assert is_nil(member.membership_fee_start_date)
    end
  end
end
