defmodule ToweropsWeb.ActivityFeedLive do
  @moduledoc """
  Live view for the organization-wide activity feed / network ops journal.
  Real-time NOC operations log with search, time grouping, and auto-refresh.
  """
  use ToweropsWeb, :live_view

  alias Towerops.ActivityFeed

  @default_limit 50
  @all_types ~w(config_change alert_fired alert_resolved device_event sync device_added)a
  @tick_interval_ms 30_000

  @impl true
  def mount(_params, _session, socket) do
    organization = socket.assigns.current_scope.organization

    if connected?(socket) do
      # Subscribe to all relevant PubSub topics for real-time updates
      Phoenix.PubSub.subscribe(Towerops.PubSub, "alerts:org:#{organization.id}:new")
      Phoenix.PubSub.subscribe(Towerops.PubSub, "alerts:org:#{organization.id}:resolved")
      Phoenix.PubSub.subscribe(Towerops.PubSub, "devices:org:#{organization.id}")
      Phoenix.PubSub.subscribe(Towerops.PubSub, "config:org:#{organization.id}")
      Phoenix.PubSub.subscribe(Towerops.PubSub, "sync:org:#{organization.id}")
      Phoenix.PubSub.subscribe(Towerops.PubSub, "device_events:org:#{organization.id}")

      # Timer for relative timestamp refresh
      :timer.send_interval(@tick_interval_ms, self(), :tick)
    end

    {:ok,
     socket
     |> assign(:page_title, "Activity Feed")
     |> assign(:active_types, @all_types)
     |> assign(:limit, @default_limit)
     |> assign(:has_more, true)
     |> assign(:search, "")
     |> assign(:now, DateTime.utc_now())
     |> load_activity()
     |> load_counts()}
  end

  @impl true
  def handle_params(_params, _url, socket) do
    {:noreply, socket}
  end

  # --- Events ---

  @impl true
  def handle_event("toggle_type", %{"type" => type_str}, socket) do
    type = String.to_existing_atom(type_str)
    active = socket.assigns.active_types

    new_active =
      if type in active do
        List.delete(active, type)
      else
        [type | active]
      end

    {:noreply,
     socket
     |> assign(:active_types, new_active)
     |> assign(:limit, @default_limit)
     |> assign(:has_more, true)
     |> load_activity()}
  end

  @impl true
  def handle_event("search", %{"search" => query}, socket) do
    {:noreply,
     socket
     |> assign(:search, query)
     |> assign(:limit, @default_limit)
     |> assign(:has_more, true)
     |> load_activity()}
  end

  @impl true
  def handle_event("clear_search", _params, socket) do
    {:noreply,
     socket
     |> assign(:search, "")
     |> assign(:limit, @default_limit)
     |> assign(:has_more, true)
     |> load_activity()}
  end

  @impl true
  def handle_event("load_more", _params, socket) do
    {:noreply,
     socket
     |> update(:limit, &(&1 + @default_limit))
     |> load_activity()}
  end

  # --- PubSub handlers ---

  @impl true
  def handle_info({:new_alert, _device_id, _alert_type}, socket) do
    {:noreply, socket |> load_activity() |> load_counts()}
  end

  @impl true
  def handle_info({:alert_resolved, _device_id, _alert_type}, socket) do
    {:noreply, socket |> load_activity() |> load_counts()}
  end

  @impl true
  def handle_info({:device_added, _device_id}, socket) do
    {:noreply, socket |> load_activity() |> load_counts()}
  end

  @impl true
  def handle_info({:config_changed, _device_id}, socket) do
    {:noreply, socket |> load_activity() |> load_counts()}
  end

  @impl true
  def handle_info({:sync_completed, _status}, socket) do
    {:noreply, socket |> load_activity() |> load_counts()}
  end

  @impl true
  def handle_info({:device_event, _device_id, _event_type}, socket) do
    {:noreply, socket |> load_activity() |> load_counts()}
  end

  @impl true
  def handle_info(:tick, socket) do
    {:noreply, assign(socket, :now, DateTime.utc_now())}
  end

  # Catch-all for unknown PubSub messages
  @impl true
  def handle_info(_msg, socket) do
    {:noreply, socket}
  end

  # --- Helpers ---

  defp filter_options do
    [
      {:config_change, "Config", "hero-wrench-screwdriver"},
      {:alert_fired, "Alerts", "hero-exclamation-triangle"},
      {:alert_resolved, "Resolved", "hero-check-circle"},
      {:device_event, "Events", "hero-bolt"},
      {:sync, "Syncs", "hero-arrow-path"},
      {:device_added, "Devices", "hero-plus-circle"}
    ]
  end

  defp filter_active_class(:config_change), do: "bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-400"
  defp filter_active_class(:alert_fired), do: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400"
  defp filter_active_class(:alert_resolved), do: "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400"
  defp filter_active_class(:device_event), do: "bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-400"
  defp filter_active_class(:sync), do: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400"
  defp filter_active_class(:device_added), do: "bg-cyan-100 text-cyan-800 dark:bg-cyan-900/30 dark:text-cyan-400"
  defp filter_active_class(_), do: "bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-300"

  defp severity_dot_color(:critical, _), do: "bg-red-500"
  defp severity_dot_color(:warning, _), do: "bg-yellow-500"
  defp severity_dot_color(:info, :alert_resolved), do: "bg-green-500"
  defp severity_dot_color(:info, :sync), do: "bg-blue-500"
  defp severity_dot_color(:info, :device_added), do: "bg-cyan-500"
  defp severity_dot_color(:info, _), do: "bg-gray-400"
  defp severity_dot_color(_, _), do: "bg-gray-400"

  defp severity_icon_color(:critical, _), do: "text-red-500 dark:text-red-400"
  defp severity_icon_color(:warning, _), do: "text-yellow-500 dark:text-yellow-400"
  defp severity_icon_color(:info, :alert_resolved), do: "text-green-500 dark:text-green-400"
  defp severity_icon_color(:info, :sync), do: "text-blue-500 dark:text-blue-400"
  defp severity_icon_color(:info, :device_added), do: "text-cyan-500 dark:text-cyan-400"
  defp severity_icon_color(_, _), do: "text-gray-400 dark:text-gray-500"

  defp severity_text_color(:critical, _), do: "text-red-700 dark:text-red-400"
  defp severity_text_color(:warning, _), do: "text-yellow-700 dark:text-yellow-400"
  defp severity_text_color(:info, :alert_resolved), do: "text-green-700 dark:text-green-400"
  defp severity_text_color(_, _), do: "text-gray-900 dark:text-white"

  defp type_badge_class(:config_change), do: "bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400"
  defp type_badge_class(:alert_fired), do: "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400"
  defp type_badge_class(:alert_resolved), do: "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400"
  defp type_badge_class(:device_event), do: "bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400"
  defp type_badge_class(:sync), do: "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
  defp type_badge_class(:device_added), do: "bg-cyan-100 text-cyan-700 dark:bg-cyan-900/30 dark:text-cyan-400"
  defp type_badge_class(_), do: "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400"

  defp type_label(:config_change), do: "Config"
  defp type_label(:alert_fired), do: "Alert"
  defp type_label(:alert_resolved), do: "Resolved"
  defp type_label(:device_event), do: "Event"
  defp type_label(:sync), do: "Sync"
  defp type_label(:device_added), do: "Device"
  defp type_label(_), do: "Other"

  defp load_activity(socket) do
    org_id = socket.assigns.current_scope.organization.id
    limit = socket.assigns.limit
    types = socket.assigns.active_types
    search = socket.assigns.search

    search_opt = if search == "", do: nil, else: search

    items =
      ActivityFeed.list_org_activity(org_id,
        limit: limit + 1,
        types: types,
        search: search_opt
      )

    has_more = length(items) > limit
    trimmed = Enum.take(items, limit)

    socket
    |> assign(:items, trimmed)
    |> assign(:grouped_items, group_by_period(trimmed))
    |> assign(:has_more, has_more)
  end

  defp load_counts(socket) do
    org_id = socket.assigns.current_scope.organization.id
    counts = ActivityFeed.count_by_type(org_id)
    assign(socket, :type_counts, counts)
  end

  @doc false
  def relative_time(timestamp, now) do
    diff = DateTime.diff(now, timestamp, :second)

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

  defp group_by_period(items) do
    today = Date.utc_today()
    yesterday = Date.add(today, -1)
    week_ago = Date.add(today, -7)

    items
    |> Enum.group_by(fn item ->
      date = DateTime.to_date(item.timestamp)

      cond do
        date == today -> "Today"
        date == yesterday -> "Yesterday"
        Date.compare(date, week_ago) != :lt -> "This Week"
        true -> "Earlier"
      end
    end)
    |> then(fn groups ->
      # Return in chronological order of periods
      for period <- ["Today", "Yesterday", "This Week", "Earlier"],
          items = Map.get(groups, period),
          items != nil and items != [],
          do: {period, items}
    end)
  end
end
