defmodule Mv.MembershipFees.CycleGenerationJob do
  @moduledoc """
  Scheduled job for generating membership fee cycles.

  This module provides a skeleton for scheduled cycle generation.
  In the future, this can be integrated with Oban or similar job processing libraries.

  ## Current Implementation

  Currently provides manual execution functions that can be called:
  - From IEx console for administrative tasks
  - From a cron job via a Mix task
  - From the admin UI (future)

  ## Future Oban Integration

  When Oban is added to the project, this module can be converted to an Oban worker:

      defmodule Mv.MembershipFees.CycleGenerationJob do
        use Oban.Worker,
          queue: :membership_fees,
          max_attempts: 3

        @impl Oban.Worker
        def perform(%Oban.Job{}) do
          Mv.MembershipFees.CycleGenerator.generate_cycles_for_all_members()
        end
      end

  ## Usage

      # Manual execution from IEx
      Mv.MembershipFees.CycleGenerationJob.run()

      # Check if cycles need to be generated
      Mv.MembershipFees.CycleGenerationJob.pending_members_count()

  """

  alias Mv.MembershipFees.CycleGenerator

  require Ash.Query
  require Logger

  @doc """
  Runs the cycle generation job for all active members.

  This is the main entry point for scheduled execution.

  ## Returns

    - `{:ok, results}` - Map with success/failed counts
    - `{:error, reason}` - Error with reason

  ## Examples

      iex> Mv.MembershipFees.CycleGenerationJob.run()
      {:ok, %{success: 45, failed: 0, total: 45}}

  """
  @spec run() :: {:ok, map()} | {:error, term()}
  def run do
    Logger.info("Starting membership fee cycle generation job")
    start_time = System.monotonic_time(:millisecond)

    result = CycleGenerator.generate_cycles_for_all_members()

    elapsed = System.monotonic_time(:millisecond) - start_time

    case result do
      {:ok, stats} ->
        Logger.info(
          "Cycle generation completed in #{elapsed}ms: #{stats.success} success, #{stats.failed} failed, #{stats.total} total"
        )

        result

      {:error, reason} ->
        Logger.error("Cycle generation failed: #{inspect(reason)}")
        result
    end
  end

  @doc """
  Runs cycle generation with custom options.

  ## Options

    - `:today` - Override today's date (useful for testing or catch-up)
    - `:batch_size` - Number of members to process in parallel

  ## Examples

      # Generate cycles as if today was a specific date
      Mv.MembershipFees.CycleGenerationJob.run(today: ~D[2024-12-31])

      # Process with smaller batch size
      Mv.MembershipFees.CycleGenerationJob.run(batch_size: 5)

  """
  @spec run(keyword()) :: {:ok, map()} | {:error, term()}
  def run(opts) when is_list(opts) do
    Logger.info("Starting membership fee cycle generation job with opts: #{inspect(opts)}")
    start_time = System.monotonic_time(:millisecond)

    result = CycleGenerator.generate_cycles_for_all_members(opts)

    elapsed = System.monotonic_time(:millisecond) - start_time

    case result do
      {:ok, stats} ->
        Logger.info(
          "Cycle generation completed in #{elapsed}ms: #{stats.success} success, #{stats.failed} failed, #{stats.total} total"
        )

        result

      {:error, reason} ->
        Logger.error("Cycle generation failed: #{inspect(reason)}")
        result
    end
  end

  @doc """
  Returns the count of members that need cycle generation.

  A member needs cycle generation if:
  - Has a membership_fee_type assigned
  - Has a join_date set
  - Is active (no exit_date or exit_date >= today)

  ## Returns

    - `{:ok, count}` - Number of members needing generation
    - `{:error, reason}` - Error with reason

  """
  @spec pending_members_count() :: {:ok, non_neg_integer()} | {:error, term()}
  def pending_members_count do
    today = Date.utc_today()

    query =
      Mv.Membership.Member
      |> Ash.Query.filter(not is_nil(membership_fee_type_id))
      |> Ash.Query.filter(not is_nil(join_date))
      |> Ash.Query.filter(is_nil(exit_date) or exit_date >= ^today)

    case Ash.count(query) do
      {:ok, count} -> {:ok, count}
      {:error, reason} -> {:error, reason}
    end
  end

  @doc """
  Generates cycles for a specific member by ID.

  Useful for administrative tasks or manual corrections.

  ## Parameters

    - `member_id` - The UUID of the member

  ## Returns

    - `{:ok, cycles}` - List of newly created cycles
    - `{:error, reason}` - Error with reason

  """
  @spec run_for_member(String.t()) :: {:ok, [map()]} | {:error, term()}
  def run_for_member(member_id) when is_binary(member_id) do
    Logger.info("Generating cycles for member #{member_id}")
    CycleGenerator.generate_cycles_for_member(member_id)
  end
end
