defmodule Berrypod.Newsletter.CampaignSendWorker do
  @moduledoc """
  Sends a campaign to all confirmed, non-suppressed subscribers.

  Processes in batches of 50 with a brief pause between batches to
  respect mail provider rate limits.
  """

  use Oban.Worker, queue: :newsletter, max_attempts: 1

  alias Berrypod.Newsletter
  alias Berrypod.Newsletter.Notifier

  require Logger

  @batch_size 50

  @impl Oban.Worker
  def perform(%Oban.Job{args: %{"campaign_id" => id}}) do
    campaign = Newsletter.get_campaign!(id)

    unless campaign.status in ["sending", "scheduled"] do
      Logger.info("Campaign #{id} status is #{campaign.status}, skipping")
      return_ok()
    end

    # Ensure status is "sending"
    {:ok, campaign} =
      if campaign.status == "scheduled" do
        Newsletter.update_campaign(campaign, %{status: "sending"})
      else
        {:ok, campaign}
      end

    subscribers = Newsletter.list_subscribers(status: "confirmed")
    {sent, failed} = send_in_batches(campaign, subscribers)

    Newsletter.update_campaign(campaign, %{
      status: "sent",
      sent_at: DateTime.utc_now() |> DateTime.truncate(:second),
      sent_count: sent,
      failed_count: failed
    })

    Logger.info("Campaign #{id} sent: #{sent} delivered, #{failed} failed")
    :ok
  end

  def enqueue(campaign_id) do
    %{campaign_id: campaign_id}
    |> new()
    |> Oban.insert()
  end

  defp send_in_batches(campaign, subscribers) do
    subscribers
    |> Enum.chunk_every(@batch_size)
    |> Enum.reduce({0, 0}, fn batch, {sent, failed} ->
      {batch_sent, batch_failed} = send_batch(campaign, batch)
      Process.sleep(100)
      {sent + batch_sent, failed + batch_failed}
    end)
  end

  defp send_batch(campaign, subscribers) do
    Enum.reduce(subscribers, {0, 0}, fn sub, {sent, failed} ->
      case Notifier.deliver_campaign(campaign, sub) do
        {:ok, _} -> {sent + 1, failed}
        {:error, _} -> {sent, failed + 1}
      end
    end)
  end

  defp return_ok, do: :ok
end
