defmodule ToweropsWeb.AlertLive.Index do
  @moduledoc """
  Smart Alert Triage view for WISP operators.

  Groups alerts by site, color-codes by severity/age, supports filtering
  and sorting, and shows subscriber impact from Gaiia inventory.
  """
  use ToweropsWeb, :live_view

  alias Towerops.Alerts
  alias Towerops.Gaiia
  alias ToweropsWeb.Live.Helpers.AccessControl

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

    _ =
      if connected?(socket) do
        _ = Phoenix.PubSub.subscribe(Towerops.PubSub, "alerts:org:#{organization.id}:new")
        Phoenix.PubSub.subscribe(Towerops.PubSub, "alerts:org:#{organization.id}:resolved")
      end

    {:ok,
     socket
     |> assign(:page_title, "Alerts")
     |> assign(:filter, "unresolved")
     |> assign(:sort_by, "severity")
     |> assign(:expanded_sites, MapSet.new())
     |> load_alerts(organization.id)}
  end

  @impl true
  def handle_params(params, _url, socket) do
    filter = Map.get(params, "filter", "unresolved")
    sort_by = Map.get(params, "sort", socket.assigns.sort_by)

    {:noreply,
     socket
     |> assign(:filter, filter)
     |> assign(:sort_by, sort_by)
     |> load_alerts(socket.assigns.current_scope.organization.id)}
  end

  @impl true
  def handle_event("acknowledge", %{"id" => id}, socket) do
    organization = socket.assigns.current_scope.organization
    user_id = socket.assigns.current_scope.user.id

    case AccessControl.verify_alert_access(id, organization.id) do
      {:ok, alert} ->
        perform_acknowledge_alert(socket, alert, user_id, organization.id)

      {:error, :not_found} ->
        {:noreply, put_flash(socket, :error, t_equipment("Alert not found"))}

      {:error, :unauthorized} ->
        {:noreply, put_flash(socket, :error, t_equipment("You don't have access to this alert"))}
    end
  end

  def handle_event("resolve", %{"id" => id}, socket) do
    organization = socket.assigns.current_scope.organization

    case AccessControl.verify_alert_access(id, organization.id) do
      {:ok, alert} ->
        case Alerts.resolve_alert(alert) do
          {:ok, _alert} ->
            {:noreply,
             socket
             |> put_flash(:info, t_equipment("Alert resolved"))
             |> load_alerts(organization.id)}

          {:error, _changeset} ->
            {:noreply, put_flash(socket, :error, t_equipment("Unable to resolve alert"))}
        end

      {:error, :not_found} ->
        {:noreply, put_flash(socket, :error, t_equipment("Alert not found"))}

      {:error, :unauthorized} ->
        {:noreply, put_flash(socket, :error, t_equipment("You don't have access to this alert"))}
    end
  end

  def handle_event("toggle_site", %{"site-id" => site_id}, socket) do
    expanded = socket.assigns.expanded_sites

    expanded =
      if MapSet.member?(expanded, site_id),
        do: MapSet.delete(expanded, site_id),
        else: MapSet.put(expanded, site_id)

    {:noreply, assign(socket, :expanded_sites, expanded)}
  end

  defp perform_acknowledge_alert(socket, alert, user_id, organization_id) do
    case Alerts.acknowledge_alert(alert, user_id) do
      {:ok, _alert} ->
        {:noreply,
         socket
         |> put_flash(:info, t_equipment("Alert acknowledged"))
         |> load_alerts(organization_id)}

      {:error, _changeset} ->
        {:noreply, put_flash(socket, :error, t_equipment("Unable to acknowledge alert"))}
    end
  end

  @impl true
  def handle_info({:new_alert, _device_id, _alert_type}, socket) do
    {:noreply, load_alerts(socket, socket.assigns.current_scope.organization.id)}
  end

  @impl true
  def handle_info({:alert_resolved, _device_id, _alert_type}, socket) do
    {:noreply, load_alerts(socket, socket.assigns.current_scope.organization.id)}
  end

  defp load_alerts(socket, organization_id) do
    all_alerts = Alerts.list_organization_alerts(organization_id, %{"limit" => 500})

    filtered_alerts = filter_alerts(all_alerts, socket.assigns.filter)
    site_subscribers = load_site_subscribers(filtered_alerts)
    sorted_alerts = sort_alerts(filtered_alerts, socket.assigns.sort_by, site_subscribers)
    grouped = group_by_site(sorted_alerts, site_subscribers)

    # Compute counts for filter tabs
    counts = %{
      all: length(all_alerts),
      critical: Enum.count(all_alerts, &(&1.alert_type == :device_down and is_nil(&1.resolved_at))),
      unresolved: Enum.count(all_alerts, &is_nil(&1.resolved_at)),
      resolved: Enum.count(all_alerts, &(not is_nil(&1.resolved_at)))
    }

    socket
    |> assign(:alerts, sorted_alerts)
    |> assign(:site_subscribers, site_subscribers)
    |> assign(:grouped_alerts, grouped)
    |> assign(:counts, counts)
  end

  defp filter_alerts(alerts, "all"), do: alerts

  defp filter_alerts(alerts, "critical"),
    do: Enum.filter(alerts, &(&1.alert_type == :device_down and is_nil(&1.resolved_at)))

  defp filter_alerts(alerts, "unresolved"),
    do: Enum.filter(alerts, &is_nil(&1.resolved_at))

  defp filter_alerts(alerts, "resolved"),
    do: Enum.filter(alerts, &(not is_nil(&1.resolved_at)))

  defp filter_alerts(alerts, _), do: alerts

  defp sort_alerts(alerts, "age", _site_subs) do
    Enum.sort_by(alerts, & &1.triggered_at, {:asc, DateTime})
  end

  defp sort_alerts(alerts, "impact", site_subs) do
    Enum.sort_by(alerts, fn alert ->
      sub_count = get_subscriber_count(alert, site_subs)
      -sub_count
    end)
  end

  defp sort_alerts(alerts, _severity, _site_subs) do
    # Default: severity sort — device_down unresolved first, then by age
    Enum.sort_by(alerts, fn alert ->
      severity_weight =
        cond do
          alert.alert_type == :device_down and is_nil(alert.resolved_at) -> 0
          alert.alert_type == :device_down -> 1
          true -> 2
        end

      {severity_weight, alert.triggered_at}
    end)
  end

  defp group_by_site(alerts, site_subscribers) do
    alerts
    |> Enum.group_by(fn alert ->
      if alert.device.site do
        {alert.device.site.id, alert.device.site.name}
      else
        {nil, "No Site"}
      end
    end)
    |> Enum.map(fn {{site_id, site_name}, site_alerts} ->
      sub_info = if site_id, do: site_subscribers[site_id], else: nil
      critical_count = Enum.count(site_alerts, &(&1.alert_type == :device_down and is_nil(&1.resolved_at)))

      %{
        site_id: site_id,
        site_name: site_name,
        alerts: site_alerts,
        total_count: length(site_alerts),
        critical_count: critical_count,
        subscriber_info: sub_info,
        total_subscribers: get_site_subscriber_count(sub_info)
      }
    end)
    |> Enum.sort_by(fn group -> {-group.critical_count, -group.total_subscribers, -group.total_count} end)
  end

  defp get_site_subscriber_count(%{account_count: count}) when is_integer(count), do: count
  defp get_site_subscriber_count(_), do: 0

  defp get_subscriber_count(alert, site_subs) do
    # Use gaiia_impact on alert first, fall back to site-level data
    cond do
      alert.gaiia_impact && alert.gaiia_impact["total_subscribers"] ->
        alert.gaiia_impact["total_subscribers"]

      alert.device.site_id && site_subs[alert.device.site_id] ->
        get_site_subscriber_count(site_subs[alert.device.site_id])

      true ->
        0
    end
  end

  defp load_site_subscribers(alerts) do
    alerts
    |> Enum.map(& &1.device.site_id)
    |> Enum.uniq()
    |> Enum.reject(&is_nil/1)
    |> Map.new(fn site_id -> {site_id, Gaiia.get_site_subscriber_summary(site_id)} end)
  end

  @doc false
  def severity_color(alert) do
    cond do
      alert.resolved_at ->
        "gray"

      alert.alert_type == :device_down and is_nil(alert.resolved_at) ->
        age_minutes = DateTime.diff(DateTime.utc_now(), alert.triggered_at, :minute)

        cond do
          age_minutes > 60 -> "red"
          age_minutes > 15 -> "orange"
          true -> "yellow"
        end

      true ->
        "green"
    end
  end

  @doc false
  def age_text(alert) do
    minutes = DateTime.diff(DateTime.utc_now(), alert.triggered_at, :minute)

    cond do
      minutes < 1 -> "just now"
      minutes < 60 -> "#{minutes}m ago"
      minutes < 1440 -> "#{div(minutes, 60)}h ago"
      true -> "#{div(minutes, 1440)}d ago"
    end
  end

  defp format_number(number) when is_integer(number) do
    number
    |> Integer.to_string()
    |> String.graphemes()
    |> Enum.reverse()
    |> Enum.chunk_every(3)
    |> Enum.join(",")
    |> String.reverse()
  end

  defp format_number(number), do: to_string(number)

  defp format_mrr(%Decimal{} = amount) do
    "$#{format_number(Decimal.to_integer(Decimal.round(amount, 0)))}"
  end

  defp format_mrr(_), do: nil
end
