defmodule BerrypodWeb.Admin.Activity do
  use BerrypodWeb, :live_view

  alias Berrypod.{ActivityLog, Orders}
  alias Berrypod.Sync.ProductSyncWorker

  @valid_tabs ~w(all attention)
  @valid_categories ~w(orders syncs emails carts)

  @impl true
  def mount(_params, _session, socket) do
    if connected?(socket), do: ActivityLog.subscribe()

    socket =
      socket
      |> assign(:page_title, "Activity")
      |> assign(:attention_count, ActivityLog.count_needing_attention())
      |> assign(:show_system, false)

    {:ok, socket}
  end

  @impl true
  def handle_params(params, _uri, socket) do
    tab = if params["tab"] in @valid_tabs, do: params["tab"], else: "all"
    category = if params["category"] in @valid_categories, do: params["category"], else: nil
    search = params["search"]
    page_num = Berrypod.Pagination.parse_page(params)

    page =
      ActivityLog.list_recent(
        page: page_num,
        tab: tab,
        category: category,
        search: search,
        hide_system: !socket.assigns.show_system
      )

    socket =
      socket
      |> assign(:tab, tab)
      |> assign(:category, category)
      |> assign(:search, search || "")
      |> assign(:pagination, page)
      |> stream(:entries, page.items, reset: true)

    {:noreply, socket}
  end

  @impl true
  def handle_info({:new_activity, entry}, socket) do
    socket = assign(socket, :attention_count, ActivityLog.count_needing_attention())

    # Only prepend to stream if on page 1 and entry matches current filters
    socket =
      if socket.assigns.pagination.page == 1 and matches_filters?(entry, socket.assigns) do
        stream_insert(socket, :entries, entry, at: 0)
      else
        socket
      end

    {:noreply, socket}
  end

  @impl true
  def handle_event("tab", %{"tab" => tab}, socket) do
    params = build_params(tab: tab)
    {:noreply, push_patch(socket, to: ~p"/admin/activity?#{params}")}
  end

  def handle_event("category", %{"category" => category}, socket) do
    # Toggle: clicking the active category clears it
    category = if category == socket.assigns.category, do: nil, else: category

    params =
      build_params(tab: socket.assigns.tab, category: category, search: socket.assigns.search)

    {:noreply, push_patch(socket, to: ~p"/admin/activity?#{params}")}
  end

  def handle_event("search", %{"search" => %{"query" => query}}, socket) do
    search = if query == "", do: nil, else: query

    params =
      build_params(tab: socket.assigns.tab, category: socket.assigns.category, search: search)

    {:noreply, push_patch(socket, to: ~p"/admin/activity?#{params}")}
  end

  def handle_event("toggle_system", _params, socket) do
    socket = assign(socket, :show_system, !socket.assigns.show_system)
    {:noreply, refetch_and_reassign(socket)}
  end

  def handle_event("resolve", %{"id" => id}, socket) do
    ActivityLog.resolve(id)
    {:noreply, refetch_and_reassign(socket)}
  end

  def handle_event("retry_submission", %{"order-id" => order_id, "entry-id" => entry_id}, socket) do
    ActivityLog.resolve(entry_id)

    case Orders.OrderSubmissionWorker.enqueue(order_id) do
      {:ok, _job} ->
        {:noreply,
         socket
         |> put_flash(:info, "Submission retry enqueued")
         |> refetch_and_reassign()}

      {:error, _reason} ->
        {:noreply, put_flash(socket, :error, "Failed to enqueue retry")}
    end
  end

  def handle_event(
        "retry_sync",
        %{"connection-id" => connection_id, "entry-id" => entry_id},
        socket
      ) do
    ActivityLog.resolve(entry_id)

    case ProductSyncWorker.enqueue(connection_id) do
      {:ok, _job} ->
        {:noreply,
         socket
         |> put_flash(:info, "Sync retry enqueued")
         |> refetch_and_reassign()}

      {:error, _reason} ->
        {:noreply, put_flash(socket, :error, "Failed to enqueue sync")}
    end
  end

  @impl true
  def render(assigns) do
    ~H"""
    <.header>
      Activity
    </.header>

    <%!-- tabs --%>
    <div class="admin-filter-row">
      <button
        phx-click="tab"
        phx-value-tab="all"
        class={[
          "admin-btn admin-btn-sm",
          @tab == "all" && "admin-btn-primary",
          @tab != "all" && "admin-btn-ghost"
        ]}
      >
        All
      </button>
      <button
        phx-click="tab"
        phx-value-tab="attention"
        class={[
          "admin-btn admin-btn-sm",
          @tab == "attention" && "admin-btn-primary",
          @tab != "attention" && "admin-btn-ghost"
        ]}
      >
        Needs attention
        <span
          :if={@attention_count > 0}
          class="admin-badge admin-badge-sm admin-badge-warning admin-badge-count"
        >
          {@attention_count}
        </span>
      </button>
    </div>

    <%!-- category chips + search --%>
    <div class="admin-filter-row admin-filter-row-between">
      <div class="admin-cluster">
        <.category_chip category={nil} active={@category} label="All" />
        <.category_chip category="orders" active={@category} label="Orders" />
        <.category_chip category="syncs" active={@category} label="Syncs" />
        <.category_chip category="emails" active={@category} label="Emails" />
        <.category_chip category="carts" active={@category} label="Carts" />
        <label class="admin-toggle-label admin-text-secondary">
          <input
            type="checkbox"
            class="admin-toggle admin-toggle-sm"
            checked={@show_system}
            phx-click="toggle_system"
          /> System events
        </label>
      </div>
      <.form
        for={%{}}
        phx-submit="search"
        as={:search}
        class="admin-row"
      >
        <input
          type="text"
          name="search[query]"
          value={@search}
          placeholder="Search by order number"
          class="admin-input admin-input-sm"
        />
        <button type="submit" class="admin-btn admin-btn-sm admin-btn-ghost">
          <.icon name="hero-magnifying-glass-mini" class="size-4" />
        </button>
      </.form>
    </div>

    <%!-- entries --%>
    <div id="activity-entries" phx-update="stream">
      <div id="activity-empty" class="admin-stream-empty">
        No activity to show.
      </div>
      <div :for={{dom_id, entry} <- @streams.entries} id={dom_id} class="admin-activity-row">
        <div class={["admin-activity-icon", activity_icon_class(entry.level)]}>
          <.icon name={activity_icon(entry.level)} class="size-4" />
        </div>
        <div class="admin-activity-body">
          <span class="admin-activity-type">{format_event_type(entry.event_type)}</span>
          <span>{entry.message}</span>
          <.link
            :if={entry.order_id}
            navigate={~p"/admin/orders/#{entry.order_id}"}
            class="admin-activity-link"
          >
            View order &rarr;
          </.link>
        </div>
        <div class="admin-activity-meta">
          <time class="admin-activity-time" datetime={DateTime.to_iso8601(entry.occurred_at)}>
            {relative_time(entry.occurred_at)}
          </time>
          <.entry_action entry={entry} />
        </div>
      </div>
    </div>

    <.admin_pagination
      page={@pagination}
      patch={~p"/admin/activity"}
      params={build_params(tab: @tab, category: @category, search: @search)}
    />
    """
  end

  # ── Components ──

  defp category_chip(assigns) do
    active = assigns.category == assigns.active

    assigns = assign(assigns, :active, active)

    ~H"""
    <button
      phx-click="category"
      phx-value-category={@category || ""}
      class={[
        "admin-btn admin-btn-xs",
        @active && "admin-btn-primary",
        !@active && "admin-btn-ghost"
      ]}
    >
      {@label}
    </button>
    """
  end

  defp entry_action(
         %{entry: %{event_type: "order.submission_failed", order_id: order_id}} = assigns
       )
       when not is_nil(order_id) do
    ~H"""
    <button
      phx-click="retry_submission"
      phx-value-order-id={@entry.order_id}
      phx-value-entry-id={@entry.id}
      class="admin-btn admin-btn-xs admin-btn-primary"
    >
      Retry submission
    </button>
    """
  end

  defp entry_action(%{entry: %{event_type: "sync.failed", payload: payload}} = assigns) do
    connection_id = payload["connection_id"] || payload[:connection_id]
    assigns = assign(assigns, :connection_id, connection_id)

    ~H"""
    <button
      :if={@connection_id}
      phx-click="retry_sync"
      phx-value-connection-id={@connection_id}
      phx-value-entry-id={@entry.id}
      class="admin-btn admin-btn-xs admin-btn-primary"
    >
      Retry sync
    </button>
    <button
      :if={!@connection_id}
      phx-click="resolve"
      phx-value-id={@entry.id}
      class="admin-btn admin-btn-xs admin-btn-ghost"
    >
      Dismiss
    </button>
    """
  end

  defp entry_action(%{entry: %{event_type: "job.exhausted", payload: payload}} = assigns) do
    # If the exhausted job was an order submission worker and we have an order_id, offer retry
    worker = payload["worker"] || payload[:worker] || ""
    order_related = String.contains?(worker, "OrderSubmission")
    assigns = assign(assigns, :order_related, order_related)

    ~H"""
    <button
      :if={@order_related && @entry.order_id}
      phx-click="retry_submission"
      phx-value-order-id={@entry.order_id}
      phx-value-entry-id={@entry.id}
      class="admin-btn admin-btn-xs admin-btn-primary"
    >
      Retry submission
    </button>
    <button
      :if={!@order_related || !@entry.order_id}
      phx-click="resolve"
      phx-value-id={@entry.id}
      class="admin-btn admin-btn-xs admin-btn-ghost"
    >
      Dismiss
    </button>
    """
  end

  defp entry_action(%{entry: %{level: level, resolved_at: nil}} = assigns)
       when level in ["warning", "error"] do
    ~H"""
    <button
      phx-click="resolve"
      phx-value-id={@entry.id}
      class="admin-btn admin-btn-xs admin-btn-ghost"
    >
      Dismiss
    </button>
    """
  end

  defp entry_action(assigns) do
    ~H""
  end

  # ── Helpers ──

  defp activity_icon("error"), do: "hero-x-circle-mini"
  defp activity_icon("warning"), do: "hero-exclamation-triangle-mini"
  defp activity_icon(_), do: "hero-check-circle-mini"

  defp activity_icon_class("error"), do: "admin-activity-icon-error"
  defp activity_icon_class("warning"), do: "admin-activity-icon-warning"
  defp activity_icon_class(_), do: "admin-activity-icon-ok"

  defp format_event_type(event_type) do
    event_type
    |> String.replace(".", " ")
    |> String.replace("_", " ")
  end

  defp relative_time(datetime) do
    now = DateTime.utc_now()
    diff = DateTime.diff(now, datetime, :second)

    cond do
      diff < 60 -> "just now"
      diff < 3600 -> "#{div(diff, 60)}m ago"
      diff < 86400 -> "#{div(diff, 3600)}h ago"
      diff < 604_800 -> "#{div(diff, 86400)}d ago"
      true -> Calendar.strftime(datetime, "%d %b %Y")
    end
  end

  defp matches_filters?(entry, assigns) do
    tab_match =
      case assigns.tab do
        "attention" -> entry.level in ["warning", "error"] and is_nil(entry.resolved_at)
        _ -> true
      end

    category_match =
      case assigns.category do
        "orders" -> String.starts_with?(entry.event_type, "order.")
        "syncs" -> String.starts_with?(entry.event_type, "sync.")
        "emails" -> String.contains?(entry.event_type, ".email.")
        "carts" -> String.starts_with?(entry.event_type, "abandoned_cart.")
        _ -> true
      end

    system_match =
      if assigns.show_system, do: true, else: !ActivityLog.system_event_type?(entry.event_type)

    tab_match and category_match and system_match
  end

  defp build_params(opts) do
    %{}
    |> then(fn p ->
      if opts[:tab] && opts[:tab] != "all", do: Map.put(p, "tab", opts[:tab]), else: p
    end)
    |> then(fn p -> if opts[:category], do: Map.put(p, "category", opts[:category]), else: p end)
    |> then(fn p -> if opts[:search], do: Map.put(p, "search", opts[:search]), else: p end)
  end

  defp refetch_page(assigns) do
    ActivityLog.list_recent(
      page: assigns.pagination.page,
      tab: assigns.tab,
      category: assigns.category,
      search: assigns.search,
      hide_system: !assigns.show_system
    )
  end

  defp refetch_and_reassign(socket) do
    page = refetch_page(socket.assigns)

    socket
    |> assign(:pagination, page)
    |> assign(:attention_count, ActivityLog.count_needing_attention())
    |> stream(:entries, page.items, reset: true)
  end
end
