defmodule Mv.Membership.MemberCycleCalculationsTest do
  @moduledoc """
  Tests for Member cycle status calculations.
  """
  use Mv.DataCase, async: true

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

  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

  # Helper to create a cycle
  defp create_cycle(member, fee_type, attrs, actor) do
    default_attrs = %{
      cycle_start: ~D[2024-01-01],
      amount: Decimal.new("50.00"),
      member_id: member.id,
      membership_fee_type_id: fee_type.id,
      status: :unpaid
    }

    attrs = Map.merge(default_attrs, attrs)

    MembershipFeeCycle
    |> Ash.Changeset.for_create(:create, attrs)
    |> Ash.create!(actor: actor)
  end

  describe "current_cycle_status" do
    test "returns status of current cycle for member with active cycle", %{actor: actor} do
      fee_type = create_fee_type(%{interval: :yearly}, actor)
      member = create_member(%{membership_fee_type_id: fee_type.id}, actor)

      # Create a cycle that is active today (2024-01-01 to 2024-12-31)
      # Assuming today is in 2024
      today = Date.utc_today()
      cycle_start = CalendarCycles.calculate_cycle_start(today, :yearly)

      create_cycle(
        member,
        fee_type,
        %{
          cycle_start: cycle_start,
          status: :paid
        },
        actor
      )

      member = Ash.load!(member, :current_cycle_status, actor: actor)
      assert member.current_cycle_status == :paid
    end

    test "returns nil for member without current cycle", %{actor: actor} do
      fee_type = create_fee_type(%{interval: :yearly}, actor)
      member = create_member(%{membership_fee_type_id: fee_type.id}, actor)

      # Create a cycle in the past (not current)
      create_cycle(
        member,
        fee_type,
        %{
          cycle_start: ~D[2020-01-01],
          status: :paid
        },
        actor
      )

      member = Ash.load!(member, :current_cycle_status, actor: actor)
      assert member.current_cycle_status == nil
    end

    test "returns nil for member without cycles", %{actor: actor} do
      fee_type = create_fee_type(%{interval: :yearly}, actor)
      member = create_member(%{membership_fee_type_id: fee_type.id}, actor)

      member = Ash.load!(member, :current_cycle_status, actor: actor)
      assert member.current_cycle_status == nil
    end

    test "returns status of current cycle for monthly interval", %{actor: actor} do
      fee_type = create_fee_type(%{interval: :monthly}, actor)
      member = create_member(%{membership_fee_type_id: fee_type.id}, actor)

      # Create a cycle that is active today (current month)
      today = Date.utc_today()
      cycle_start = CalendarCycles.calculate_cycle_start(today, :monthly)

      create_cycle(
        member,
        fee_type,
        %{
          cycle_start: cycle_start,
          status: :unpaid
        },
        actor
      )

      member = Ash.load!(member, :current_cycle_status, actor: actor)
      assert member.current_cycle_status == :unpaid
    end
  end

  describe "last_cycle_status" do
    test "returns status of last completed cycle", %{actor: actor} do
      fee_type = create_fee_type(%{interval: :yearly}, actor)
      member = create_member(%{membership_fee_type_id: fee_type.id}, actor)

      # Create cycles: 2022 (completed), 2023 (completed), 2024 (current)
      today = Date.utc_today()

      create_cycle(
        member,
        fee_type,
        %{
          cycle_start: ~D[2022-01-01],
          status: :paid
        },
        actor
      )

      create_cycle(
        member,
        fee_type,
        %{
          cycle_start: ~D[2023-01-01],
          status: :unpaid
        },
        actor
      )

      # Current cycle
      cycle_start = CalendarCycles.calculate_cycle_start(today, :yearly)

      create_cycle(
        member,
        fee_type,
        %{
          cycle_start: cycle_start,
          status: :paid
        },
        actor
      )

      member = Ash.load!(member, :last_cycle_status, actor: actor)
      # Should return status of 2023 (last completed)
      assert member.last_cycle_status == :unpaid
    end

    test "returns nil for member without completed cycles", %{actor: actor} do
      fee_type = create_fee_type(%{interval: :yearly}, actor)
      member = create_member(%{membership_fee_type_id: fee_type.id}, actor)

      # Only create current cycle (not completed yet)
      today = Date.utc_today()
      cycle_start = CalendarCycles.calculate_cycle_start(today, :yearly)

      create_cycle(
        member,
        fee_type,
        %{
          cycle_start: cycle_start,
          status: :paid
        },
        actor
      )

      member = Ash.load!(member, :last_cycle_status, actor: actor)
      assert member.last_cycle_status == nil
    end

    test "returns nil for member without cycles", %{actor: actor} do
      fee_type = create_fee_type(%{interval: :yearly}, actor)
      member = create_member(%{membership_fee_type_id: fee_type.id}, actor)

      member = Ash.load!(member, :last_cycle_status, actor: actor)
      assert member.last_cycle_status == nil
    end

    test "returns status of last completed cycle for monthly interval", %{actor: actor} do
      fee_type = create_fee_type(%{interval: :monthly}, actor)
      member = create_member(%{membership_fee_type_id: fee_type.id}, actor)

      today = Date.utc_today()
      # Create cycles: last month (completed), current month (not completed)
      last_month_start = Date.add(today, -32) |> CalendarCycles.calculate_cycle_start(:monthly)
      current_month_start = CalendarCycles.calculate_cycle_start(today, :monthly)

      create_cycle(
        member,
        fee_type,
        %{
          cycle_start: last_month_start,
          status: :paid
        },
        actor
      )

      create_cycle(
        member,
        fee_type,
        %{
          cycle_start: current_month_start,
          status: :unpaid
        },
        actor
      )

      member = Ash.load!(member, :last_cycle_status, actor: actor)
      # Should return status of last month (last completed)
      assert member.last_cycle_status == :paid
    end
  end

  describe "overdue_count" do
    test "counts only unpaid cycles that have ended", %{actor: actor} do
      fee_type = create_fee_type(%{interval: :yearly}, actor)
      member = create_member(%{membership_fee_type_id: fee_type.id}, actor)

      today = Date.utc_today()

      # Create cycles:
      # 2022: unpaid, ended (overdue)
      # 2023: paid, ended (not overdue)
      # 2024: unpaid, current (not overdue)
      # 2025: unpaid, future (not overdue)

      create_cycle(
        member,
        fee_type,
        %{
          cycle_start: ~D[2022-01-01],
          status: :unpaid
        },
        actor
      )

      create_cycle(
        member,
        fee_type,
        %{
          cycle_start: ~D[2023-01-01],
          status: :paid
        },
        actor
      )

      # Current cycle
      cycle_start = CalendarCycles.calculate_cycle_start(today, :yearly)

      create_cycle(
        member,
        fee_type,
        %{
          cycle_start: cycle_start,
          status: :unpaid
        },
        actor
      )

      # Future cycle (if we're not at the end of the year)
      next_year = today.year + 1

      if today.month < 12 or today.day < 31 do
        next_year_start = Date.new!(next_year, 1, 1)

        create_cycle(
          member,
          fee_type,
          %{
            cycle_start: next_year_start,
            status: :unpaid
          },
          actor
        )
      end

      member = Ash.load!(member, :overdue_count, actor: actor)
      # Should only count 2022 (unpaid and ended)
      assert member.overdue_count == 1
    end

    test "returns 0 when no overdue cycles", %{actor: actor} do
      fee_type = create_fee_type(%{interval: :yearly}, actor)
      member = create_member(%{membership_fee_type_id: fee_type.id}, actor)

      # Create only paid cycles
      create_cycle(
        member,
        fee_type,
        %{
          cycle_start: ~D[2022-01-01],
          status: :paid
        },
        actor
      )

      member = Ash.load!(member, :overdue_count, actor: actor)
      assert member.overdue_count == 0
    end

    test "returns 0 for member without cycles", %{actor: actor} do
      fee_type = create_fee_type(%{interval: :yearly}, actor)
      member = create_member(%{membership_fee_type_id: fee_type.id}, actor)

      member = Ash.load!(member, :overdue_count, actor: actor)
      assert member.overdue_count == 0
    end

    test "counts overdue cycles for monthly interval", %{actor: actor} do
      fee_type = create_fee_type(%{interval: :monthly}, actor)
      member = create_member(%{membership_fee_type_id: fee_type.id}, actor)

      today = Date.utc_today()

      # Create cycles: two months ago (unpaid, ended), last month (paid, ended), current month (unpaid, not ended)
      two_months_ago_start =
        Date.add(today, -65) |> CalendarCycles.calculate_cycle_start(:monthly)

      last_month_start = Date.add(today, -32) |> CalendarCycles.calculate_cycle_start(:monthly)
      current_month_start = CalendarCycles.calculate_cycle_start(today, :monthly)

      create_cycle(
        member,
        fee_type,
        %{
          cycle_start: two_months_ago_start,
          status: :unpaid
        },
        actor
      )

      create_cycle(
        member,
        fee_type,
        %{
          cycle_start: last_month_start,
          status: :paid
        },
        actor
      )

      create_cycle(
        member,
        fee_type,
        %{
          cycle_start: current_month_start,
          status: :unpaid
        },
        actor
      )

      member = Ash.load!(member, :overdue_count, actor: actor)
      # Should only count two_months_ago (unpaid and ended)
      assert member.overdue_count == 1
    end

    test "counts multiple overdue cycles", %{actor: actor} do
      fee_type = create_fee_type(%{interval: :yearly}, actor)
      member = create_member(%{membership_fee_type_id: fee_type.id}, actor)

      # Create multiple unpaid, ended cycles
      create_cycle(
        member,
        fee_type,
        %{
          cycle_start: ~D[2020-01-01],
          status: :unpaid
        },
        actor
      )

      create_cycle(
        member,
        fee_type,
        %{
          cycle_start: ~D[2021-01-01],
          status: :unpaid
        },
        actor
      )

      create_cycle(
        member,
        fee_type,
        %{
          cycle_start: ~D[2022-01-01],
          status: :unpaid
        },
        actor
      )

      member = Ash.load!(member, :overdue_count, actor: actor)
      assert member.overdue_count == 3
    end
  end

  describe "calculations with multiple cycles" do
    test "all calculations work correctly with multiple cycles", %{actor: actor} do
      fee_type = create_fee_type(%{interval: :yearly}, actor)
      member = create_member(%{membership_fee_type_id: fee_type.id}, actor)

      today = Date.utc_today()

      # Create cycles: 2022 (unpaid, ended), 2023 (paid, ended), 2024 (unpaid, current)
      create_cycle(
        member,
        fee_type,
        %{
          cycle_start: ~D[2022-01-01],
          status: :unpaid
        },
        actor
      )

      create_cycle(
        member,
        fee_type,
        %{
          cycle_start: ~D[2023-01-01],
          status: :paid
        },
        actor
      )

      cycle_start = CalendarCycles.calculate_cycle_start(today, :yearly)

      create_cycle(
        member,
        fee_type,
        %{
          cycle_start: cycle_start,
          status: :unpaid
        },
        actor
      )

      member =
        Ash.load!(member, [:current_cycle_status, :last_cycle_status, :overdue_count],
          actor: actor
        )

      assert member.current_cycle_status == :unpaid
      assert member.last_cycle_status == :paid
      assert member.overdue_count == 1
    end
  end
end
