defmodule Berrypod.ActivityLog do
  @moduledoc """
  Records and queries meaningful system events (orders, syncs, emails,
  abandoned carts). Powers the order timeline and global activity feed.
  """

  import Ecto.Query

  require Logger

  alias Berrypod.Repo
  alias Berrypod.ActivityLog.Entry

  @system_event_types ~w(sync.started sync.completed sync.failed job.exhausted abandoned_cart.created)

  @doc "Returns true if the event type is internal system noise."
  def system_event_type?(event_type), do: event_type in @system_event_types

  # ── Write ──

  @doc """
  Records an activity event. Fire-and-forget — never raises, never crashes
  the calling process.

  ## Options

    * `:level` - "info" (default), "warning", or "error"
    * `:order_id` - links the event to an order
    * `:payload` - arbitrary map of snapshot data
    * `:occurred_at` - when the event happened (defaults to now)

  """
  def log_event(event_type, message, opts \\ []) do
    attrs = %{
      event_type: event_type,
      level: opts[:level] || "info",
      order_id: opts[:order_id],
      payload: opts[:payload] || %{},
      message: message,
      occurred_at: opts[:occurred_at] || DateTime.utc_now() |> DateTime.truncate(:second)
    }

    case %Entry{} |> Entry.changeset(attrs) |> Repo.insert() do
      {:ok, entry} ->
        broadcast(entry)
        {:ok, entry}

      {:error, changeset} ->
        Logger.warning("ActivityLog insert failed: #{inspect(changeset.errors)}")
        :ok
    end
  rescue
    e ->
      Logger.warning("ActivityLog insert crashed: #{Exception.message(e)}")
      :ok
  end

  # ── Read ──

  @doc """
  Returns all activity entries for an order, oldest first.
  """
  def list_for_order(order_id) do
    Entry
    |> where([a], a.order_id == ^order_id)
    |> order_by([a], asc: a.occurred_at)
    |> Repo.all()
  end

  @doc """
  Paginated activity feed for the global activity page.

  ## Options

    * `:page` - page number (default 1)
    * `:tab` - "all" (default) or "attention" (unresolved warnings/errors)
    * `:category` - "orders", "syncs", "emails", "carts", or nil for all
    * `:search` - order number substring search
    * `:hide_system` - exclude system event types (default false)

  """
  def list_recent(opts \\ []) do
    Entry
    |> order_by([a], desc: a.occurred_at)
    |> maybe_filter_tab(opts[:tab])
    |> maybe_filter_category(opts[:category])
    |> maybe_hide_system(opts[:hide_system])
    |> maybe_search_order_number(opts[:search])
    |> Berrypod.Pagination.paginate(page: opts[:page], per_page: 50)
  end

  @doc """
  Count of unresolved warnings and errors.
  """
  def count_needing_attention do
    Entry
    |> where([a], a.level in ["warning", "error"] and is_nil(a.resolved_at))
    |> Repo.aggregate(:count)
  end

  # ── Resolve ──

  @doc """
  Mark a single entry as resolved.
  """
  def resolve(id) do
    Entry
    |> where([a], a.id == ^id)
    |> Repo.update_all(set: [resolved_at: DateTime.utc_now() |> DateTime.truncate(:second)])
  end

  @doc """
  Resolve all unresolved entries for an order.
  """
  def resolve_all_for_order(order_id) do
    Entry
    |> where([a], a.order_id == ^order_id and is_nil(a.resolved_at))
    |> Repo.update_all(set: [resolved_at: DateTime.utc_now() |> DateTime.truncate(:second)])
  end

  # ── PubSub ──

  @doc """
  Subscribe to all activity events.
  """
  def subscribe do
    Phoenix.PubSub.subscribe(Berrypod.PubSub, "activity:all")
  end

  @doc """
  Subscribe to activity events for a specific order.
  """
  def subscribe(order_id) do
    Phoenix.PubSub.subscribe(Berrypod.PubSub, "activity:order:#{order_id}")
  end

  # ── Private ──

  defp broadcast(entry) do
    Phoenix.PubSub.broadcast(Berrypod.PubSub, "activity:all", {:new_activity, entry})

    if entry.order_id do
      Phoenix.PubSub.broadcast(
        Berrypod.PubSub,
        "activity:order:#{entry.order_id}",
        {:new_activity, entry}
      )
    end
  end

  defp maybe_filter_tab(query, "attention") do
    where(query, [a], a.level in ["warning", "error"] and is_nil(a.resolved_at))
  end

  defp maybe_filter_tab(query, _), do: query

  defp maybe_filter_category(query, "orders") do
    where(query, [a], like(a.event_type, "order.%"))
  end

  defp maybe_filter_category(query, "syncs") do
    where(query, [a], like(a.event_type, "sync.%"))
  end

  defp maybe_filter_category(query, "emails") do
    where(query, [a], like(a.event_type, "%.email.%"))
  end

  defp maybe_filter_category(query, "carts") do
    where(query, [a], like(a.event_type, "abandoned_cart.%"))
  end

  defp maybe_filter_category(query, _), do: query

  defp maybe_hide_system(query, true) do
    where(query, [a], a.event_type not in ^@system_event_types)
  end

  defp maybe_hide_system(query, _), do: query

  defp maybe_search_order_number(query, search) when is_binary(search) and search != "" do
    query
    |> join(:inner, [a], o in "orders", on: a.order_id == o.id)
    |> where([_a, o], like(o.order_number, ^"%#{search}%"))
  end

  defp maybe_search_order_number(query, _), do: query
end
