defmodule ToweropsWeb.DashboardLive do
  @moduledoc false
  use ToweropsWeb, :live_view

  alias Towerops.ActivityFeed
  alias Towerops.Alerts
  alias Towerops.Dashboard
  alias Towerops.Devices
  alias Towerops.Preseem
  alias Towerops.Preseem.Insights
  alias Towerops.Sites
  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

    status_counts = Devices.get_device_status_counts(organization.id)
    device_count = status_counts |> Map.values() |> Enum.sum()

    {:ok,
     socket
     |> assign(:page_title, organization.name)
     |> assign(:device_count, device_count)
     |> assign(:insight_source, nil)}
  end

  @impl true
  def handle_params(params, _url, socket) do
    organization = socket.assigns.current_scope.organization
    insight_source = params["insight_source"]

    {:noreply,
     socket
     |> assign(:insight_source, insight_source)
     |> load_dashboard_data(organization.id)}
  end

  @impl true
  def handle_event("refresh_dashboard", _params, socket) do
    organization = socket.assigns.current_scope.organization
    {:noreply, load_dashboard_data(socket, organization.id)}
  end

  @impl true
  def handle_event("dismiss_insight", %{"id" => insight_id}, socket) do
    case Preseem.dismiss_insight(insight_id) do
      {:ok, _} ->
        {:noreply,
         socket
         |> load_insights(socket.assigns.current_scope.organization.id)
         |> put_flash(:info, "Insight dismissed")}

      {:error, _} ->
        {:noreply, put_flash(socket, :error, "Failed to dismiss insight")}
    end
  end

  @impl true
  def handle_event("acknowledge_alert", %{"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} ->
        case Alerts.acknowledge_alert(alert, user_id) do
          {:ok, _} ->
            {:noreply,
             socket
             |> load_dashboard_data(organization.id)
             |> put_flash(:info, "Alert acknowledged")}

          {:error, _} ->
            {:noreply, put_flash(socket, :error, "Failed to acknowledge alert")}
        end

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

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

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

  defp load_dashboard_data(socket, organization_id) do
    organization = socket.assigns.current_scope.organization
    summary = Dashboard.get_dashboard_summary(organization_id)
    active_alerts = Alerts.list_organization_active_alerts(organization_id)
    sites_count = if organization.use_sites, do: Sites.count_organization_sites(organization_id), else: 0

    status_counts = Devices.get_device_status_counts(organization_id)
    device_count = status_counts |> Map.values() |> Enum.sum()

    has_subscribers = summary.subscribers.total > 0

    site_summaries =
      if organization.use_sites do
        Dashboard.list_site_summaries(organization_id)
      else
        []
      end

    # Impact data
    impact_summary = Dashboard.get_impact_summary(organization_id)
    active_incidents = Dashboard.list_active_incidents(organization_id)

    site_impact_summaries =
      if organization.use_sites do
        Dashboard.get_site_impact_summaries(organization_id)
      else
        []
      end

    device_up = Map.get(status_counts, :up, 0)

    uptime_percentage =
      if device_count > 0,
        do: Float.round(device_up / device_count * 100, 1),
        else: 100.0

    recent_activity =
      try do
        ActivityFeed.list_org_activity(organization_id, limit: 5)
      rescue
        _ -> []
      end

    socket
    |> assign(:summary, summary)
    |> assign(:active_alerts, Enum.take(active_alerts, 20))
    |> assign(:total_alert_count, length(active_alerts))
    |> assign(:sites_count, sites_count)
    |> assign(:device_count, device_count)
    |> assign(:device_up, device_up)
    |> assign(:device_down, Map.get(status_counts, :down, 0))
    |> assign(:device_unknown, Map.get(status_counts, :unknown, 0))
    |> assign(:has_subscribers, has_subscribers)
    |> assign(:site_summaries, site_summaries)
    |> assign(:impact_summary, impact_summary)
    |> assign(:active_incidents, active_incidents)
    |> assign(:site_impact_summaries, site_impact_summaries)
    |> assign(:uptime_percentage, uptime_percentage)
    |> assign(:recent_activity, recent_activity)
    |> assign(:last_updated, DateTime.utc_now())
    |> load_insights(organization_id)
    |> load_recent_config_changes(organization_id)
  end

  defp load_recent_config_changes(socket, organization_id) do
    alias Towerops.ConfigChanges

    changes =
      ConfigChanges.list_org_changes(organization_id,
        limit: 10,
        after: DateTime.add(DateTime.utc_now(), -7 * 24 * 3600, :second),
        preload: [:device]
      )

    assign(socket, :recent_config_changes, changes)
  end

  defp load_insights(socket, organization_id) do
    opts = [status: "active", limit: 15]

    opts =
      if socket.assigns.insight_source,
        do: Keyword.put(opts, :source, socket.assigns.insight_source),
        else: opts

    insights = Insights.list_insights(organization_id, opts)
    assign(socket, :insights, insights)
  end

  defp health_score_color(score) when score > 80, do: "text-green-600 dark:text-green-400"
  defp health_score_color(score) when score > 50, do: "text-yellow-600 dark:text-yellow-400"
  defp health_score_color(_score), do: "text-red-600 dark:text-red-400"

  defp health_score_bg(score) when score > 80,
    do: "bg-green-50 border-green-200 dark:bg-green-900/20 dark:border-green-800"

  defp health_score_bg(score) when score > 50,
    do: "bg-yellow-50 border-yellow-200 dark:bg-yellow-900/20 dark:border-yellow-800"

  defp health_score_bg(_score), do: "bg-red-50 border-red-200 dark:bg-red-900/20 dark:border-red-800"

  defp urgency_classes("critical"), do: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400"
  defp urgency_classes("warning"), do: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400"
  defp urgency_classes("info"), do: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400"
  defp urgency_classes(_), do: "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400"

  defp source_classes("preseem"), do: "bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-400"
  defp source_classes("snmp"), do: "bg-cyan-100 text-cyan-800 dark:bg-cyan-900/30 dark:text-cyan-400"
  defp source_classes("gaiia"), do: "bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-400"
  defp source_classes("system"), do: "bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300"
  defp source_classes(_), do: "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400"

  defp config_change_impact_color(change) do
    cond do
      change.change_size > 50 -> "bg-red-500"
      change.change_size > 20 -> "bg-yellow-500"
      true -> "bg-green-500"
    end
  end

  defp format_mrr(nil), do: "$0"

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

  defp format_mrr(amount) when is_number(amount), do: "$#{format_number(round(amount))}"
  defp format_mrr(_), do: "$0"

  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_duration(nil), do: "Unknown"

  defp format_duration(seconds) when is_integer(seconds) do
    cond do
      seconds < 60 -> "#{seconds}s"
      seconds < 3600 -> "#{div(seconds, 60)}m"
      seconds < 86400 -> "#{div(seconds, 3600)}h #{div(rem(seconds, 3600), 60)}m"
      true -> "#{div(seconds, 86400)}d #{div(rem(seconds, 86400), 3600)}h"
    end
  end

  defp format_duration(_), do: "Unknown"

  defp incident_severity_classes(mrr) do
    amount = decimal_to_float(mrr)

    cond do
      amount >= 1000 -> "border-l-4 border-red-500 bg-red-50 dark:bg-red-950/30"
      amount >= 100 -> "border-l-4 border-orange-500 bg-orange-50 dark:bg-orange-950/30"
      amount > 0 -> "border-l-4 border-yellow-500 bg-yellow-50 dark:bg-yellow-950/30"
      true -> "border-l-4 border-gray-300 bg-gray-50 dark:bg-gray-800/50"
    end
  end

  defp site_health_border(:red), do: "border-red-300 bg-red-50/50 dark:border-red-800 dark:bg-red-950/20"
  defp site_health_border(:yellow), do: "border-yellow-300 bg-yellow-50/50 dark:border-yellow-800 dark:bg-yellow-950/20"
  defp site_health_border(_), do: "border-gray-200 bg-white dark:border-white/10 dark:bg-gray-800/50"

  defp site_health_dot(:red), do: "bg-red-500"
  defp site_health_dot(:yellow), do: "bg-yellow-500"
  defp site_health_dot(_), do: "bg-green-500"

  defp decimal_to_float(%Decimal{} = d), do: Decimal.to_float(d)
  defp decimal_to_float(n) when is_number(n), do: n / 1
  defp decimal_to_float(_), do: 0.0

  defp mrr_at_risk_positive?(%Decimal{} = d), do: Decimal.gt?(d, Decimal.new("0"))
  defp mrr_at_risk_positive?(n) when is_number(n), do: n > 0
  defp mrr_at_risk_positive?(_), do: false

  defp uptime_color(pct) when pct >= 99.0, do: "text-green-600 dark:text-green-400"
  defp uptime_color(pct) when pct >= 95.0, do: "text-yellow-600 dark:text-yellow-400"
  defp uptime_color(_pct), do: "text-red-600 dark:text-red-400"

  defp uptime_bg(pct) when pct >= 99.0,
    do: "bg-green-50 border-green-200 dark:bg-green-900/20 dark:border-green-800"

  defp uptime_bg(pct) when pct >= 95.0,
    do: "bg-yellow-50 border-yellow-200 dark:bg-yellow-900/20 dark:border-yellow-800"

  defp uptime_bg(_pct),
    do: "bg-red-50 border-red-200 dark:bg-red-900/20 dark:border-red-800"

  defp format_qoe(nil), do: "—"
  defp format_qoe(score), do: :erlang.float_to_binary(score / 1, decimals: 1)
end
