defmodule ToweropsWeb.AgentLive.Index do
  @moduledoc false
  use ToweropsWeb, :live_view
  use Gettext, backend: ToweropsWeb.Gettext

  import ToweropsWeb.AgentLive.Helpers
  import ToweropsWeb.GettextHelpers

  alias Towerops.Accounts.Scope
  alias Towerops.Agents
  alias Towerops.Agents.Stats
  alias Towerops.Settings

  @impl true
  def mount(_params, _session, socket) do
    organization = socket.assigns.current_scope.organization
    current_scope = socket.assigns.current_scope
    is_superuser = Scope.superuser?(current_scope)
    agent_tokens = Agents.list_organization_agent_tokens(organization.id)

    # Subscribe to agent health updates for real-time status changes
    if connected?(socket) do
      _ = Phoenix.PubSub.subscribe(Towerops.PubSub, "agents:health")
      :timer.send_interval(1000, :tick)
    end

    # If superadmin (including when impersonating), also load cloud pollers and global default
    {cloud_pollers, global_default_cloud_poller_id} =
      if is_superuser do
        {Agents.list_cloud_pollers(), Settings.get_global_default_cloud_poller()}
      else
        {[], nil}
      end

    # device counts for each agent (both direct and total with inheritance)
    equipment_counts = calculate_device_counts(agent_tokens)

    # device counts for cloud pollers (if superuser)
    cloud_poller_counts =
      if is_superuser do
        calculate_device_counts(cloud_pollers)
      else
        %{}
      end

    # Get organization-wide agent health statistics
    agent_health_stats = Stats.get_organization_agent_health(organization.id)
    assignment_breakdown = Stats.get_device_assignment_breakdown(organization.id)
    offline_agents = Stats.get_offline_agents(organization.id)

    # Get agent image URL from config or use default
    agent_image = Application.get_env(:towerops, :agent_docker_image, "towerops/agent:latest")

    {:ok,
     socket
     |> assign(:page_title, "Remote Agents")
     |> assign(:timezone, socket.assigns.current_scope.timezone)
     |> assign(:is_superuser, is_superuser)
     |> stream(:agent_tokens, agent_tokens)
     |> assign(:has_agents, agent_tokens != [])
     |> stream(:cloud_pollers, cloud_pollers)
     |> assign(:cloud_pollers_list, cloud_pollers)
     |> assign(:has_cloud_pollers, cloud_pollers != [])
     |> assign(:global_default_cloud_poller_id, global_default_cloud_poller_id)
     |> assign(:selected_global_default, global_default_cloud_poller_id || "")
     |> assign(:device_counts, equipment_counts)
     |> assign(:cloud_poller_counts, cloud_poller_counts)
     |> assign(:agent_health_stats, agent_health_stats)
     |> assign(:assignment_breakdown, assignment_breakdown)
     |> assign(:offline_agents, offline_agents)
     |> assign(:agent_image, agent_image)
     |> assign(:new_token, nil)
     |> assign(:show_token_modal, false)
     |> assign(:now, DateTime.utc_now())
     |> assign(:agent_form, to_form(%{"name" => "", "is_cloud_poller" => false}))}
  end

  @impl true
  def handle_params(params, _url, socket) do
    # Read modal state from URL
    modal = params["modal"]

    socket =
      socket
      |> assign(:show_token_modal, modal == "setup")
      |> apply_action(socket.assigns.live_action, params)

    {:noreply, socket}
  end

  @impl true
  def handle_event("create_agent", params, socket) do
    {name, is_cloud_poller} = parse_agent_params(params)

    with :ok <- validate_cloud_poller_permission(socket.assigns.current_scope, is_cloud_poller),
         {:ok, agent_token, token} <- create_agent(socket.assigns.current_scope.organization, name, is_cloud_poller) do
      handle_agent_creation_success(socket, agent_token, token, is_cloud_poller)
    else
      {:error, :unauthorized} ->
        {:noreply, put_flash(socket, :error, t_equipment("Only superadmins can create cloud pollers"))}

      {:error, _changeset} ->
        error_message =
          if is_cloud_poller,
            do: t_equipment("Failed to create cloud poller"),
            else: t_equipment("Failed to create agent")

        {:noreply, put_flash(socket, :error, error_message)}
    end
  end

  @impl true
  def handle_event("close_token_modal", _params, socket) do
    {:noreply,
     socket
     |> assign(:new_token, nil)
     |> push_patch(to: ~p"/agents")}
  end

  @impl true
  def handle_event("show_setup", %{"id" => id}, socket) do
    agent_token = Agents.get_agent_token!(id)

    {:noreply,
     socket
     |> assign(:new_token, %{agent_token: agent_token, token: agent_token.token})
     |> push_patch(to: ~p"/agents?modal=setup")}
  end

  @impl true
  def handle_event("regenerate_token", %{"id" => id}, socket) do
    agent_token = Agents.get_agent_token!(id)
    organization = socket.assigns.current_scope.organization

    # Revoke the old token and create a new one
    case Agents.revoke_agent_token(id) do
      {:ok, _} ->
        case Agents.create_agent_token(organization.id, agent_token.name) do
          {:ok, new_agent_token, token} ->
            agent_tokens = Agents.list_organization_agent_tokens(organization.id)

            {:noreply,
             socket
             |> stream(:agent_tokens, agent_tokens, reset: true)
             |> assign(:has_agents, agent_tokens != [])
             |> assign(:new_token, %{agent_token: new_agent_token, token: token})
             |> assign(:show_token_modal, true)
             |> put_flash(:info, t_equipment("New token generated successfully"))}

          {:error, _changeset} ->
            {:noreply, put_flash(socket, :error, t_equipment("Failed to generate new token"))}
        end

      {:error, _} ->
        {:noreply, put_flash(socket, :error, t_equipment("Failed to revoke old token"))}
    end
  end

  @impl true
  def handle_event("delete_agent", %{"id" => id}, socket) do
    case Agents.delete_agent_token(id) do
      {:ok, _} ->
        organization = socket.assigns.current_scope.organization

        # If the deleted agent was the global default, clear it
        if Scope.superuser?(socket.assigns.current_scope) &&
             socket.assigns.global_default_cloud_poller_id == id do
          Settings.set_global_default_cloud_poller(nil)
        end

        agent_tokens = Agents.list_organization_agent_tokens(organization.id)

        # Refresh cloud pollers list if superadmin (in case a cloud poller was deleted)
        cloud_pollers = load_cloud_pollers_if_superuser(socket.assigns.current_scope)

        # Reload global default cloud poller ID
        global_default_cloud_poller_id =
          if Scope.superuser?(socket.assigns.current_scope) do
            Settings.get_global_default_cloud_poller()
          else
            socket.assigns.global_default_cloud_poller_id
          end

        # Recalculate device counts after deletion
        equipment_counts =
          Map.new(agent_tokens, fn token ->
            direct = Agents.count_assigned_devices(token.id)
            total = length(Agents.list_agent_polling_targets(token.id))
            {token.id, %{direct: direct, total: total}}
          end)

        cloud_poller_counts = calculate_device_counts(cloud_pollers)

        # Refresh health statistics
        agent_health_stats = Stats.get_organization_agent_health(organization.id)
        assignment_breakdown = Stats.get_device_assignment_breakdown(organization.id)
        offline_agents = Stats.get_offline_agents(organization.id)

        {:noreply,
         socket
         |> stream(:agent_tokens, agent_tokens, reset: true)
         |> assign(:has_agents, agent_tokens != [])
         |> stream(:cloud_pollers, cloud_pollers, reset: true)
         |> assign(:cloud_pollers_list, cloud_pollers)
         |> assign(:has_cloud_pollers, cloud_pollers != [])
         |> assign(:global_default_cloud_poller_id, global_default_cloud_poller_id)
         |> assign(:selected_global_default, global_default_cloud_poller_id || "")
         |> assign(:device_counts, equipment_counts)
         |> assign(:cloud_poller_counts, cloud_poller_counts)
         |> assign(:agent_health_stats, agent_health_stats)
         |> assign(:assignment_breakdown, assignment_breakdown)
         |> assign(:offline_agents, offline_agents)
         |> put_flash(
           :info,
           t_equipment("Agent deleted successfully. Devices now fall back to site/org defaults or cloud polling.")
         )}

      {:error, _} ->
        {:noreply, put_flash(socket, :error, t_equipment("Failed to delete agent"))}
    end
  end

  @impl true
  def handle_event("update_selected_global_default", %{"agent_token_id" => agent_token_id}, socket) do
    # Just update the selected value in the dropdown, don't save yet
    {:noreply, assign(socket, :selected_global_default, agent_token_id)}
  end

  @impl true
  def handle_event("save_global_default", _params, socket) do
    current_scope = socket.assigns.current_scope

    if Scope.superuser?(current_scope) do
      agent_token_id = socket.assigns.selected_global_default
      # Handle empty string as nil
      agent_token_id = if agent_token_id == "", do: nil, else: agent_token_id

      # Validate that the agent exists if not nil
      case validate_and_save_global_default(agent_token_id) do
        {:ok, validated_id} ->
          handle_global_default_success(socket, validated_id)

        {:error, :agent_not_found} ->
          {:noreply,
           put_flash(socket, :error, t_equipment("Selected agent no longer exists. Please choose another agent."))}

        {:error, _} ->
          {:noreply, put_flash(socket, :error, t_equipment("Failed to update global default cloud poller"))}
      end
    else
      {:noreply, put_flash(socket, :error, t_equipment("Only superadmins can set the global default cloud poller"))}
    end
  end

  defp apply_action(socket, :index, _params) do
    socket
  end

  defp parse_agent_params(params) do
    case params do
      %{"agent_form" => %{"name" => n, "is_cloud_poller" => cp}} ->
        {n, cp in ["true", "on", true]}

      %{"agent_form" => %{"name" => n}} ->
        {n, false}

      %{"name" => n, "is_cloud_poller" => cp} ->
        {n, cp in ["true", "on", true]}

      %{"name" => n} ->
        {n, false}
    end
  end

  defp validate_cloud_poller_permission(scope, is_cloud_poller) do
    if is_cloud_poller && !Scope.superuser?(scope) do
      {:error, :unauthorized}
    else
      :ok
    end
  end

  defp create_agent(organization, name, is_cloud_poller) do
    if is_cloud_poller do
      Agents.create_cloud_poller(name)
    else
      Agents.create_agent_token(organization.id, name)
    end
  end

  defp handle_agent_creation_success(socket, agent_token, token, is_cloud_poller) do
    organization = socket.assigns.current_scope.organization
    agent_tokens = Agents.list_organization_agent_tokens(organization.id)

    cloud_pollers = load_cloud_pollers_if_superuser(socket.assigns.current_scope)

    equipment_counts = calculate_device_counts(agent_tokens)
    cloud_poller_counts = calculate_device_counts(cloud_pollers)

    agent_health_stats = Stats.get_organization_agent_health(organization.id)
    assignment_breakdown = Stats.get_device_assignment_breakdown(organization.id)
    offline_agents = Stats.get_offline_agents(organization.id)

    success_message = get_success_message(is_cloud_poller)

    {:noreply,
     socket
     |> stream(:agent_tokens, agent_tokens, reset: true)
     |> assign(:has_agents, agent_tokens != [])
     |> stream(:cloud_pollers, cloud_pollers, reset: true)
     |> assign(:cloud_pollers_list, cloud_pollers)
     |> assign(:has_cloud_pollers, cloud_pollers != [])
     |> assign(:device_counts, equipment_counts)
     |> assign(:cloud_poller_counts, cloud_poller_counts)
     |> assign(:agent_health_stats, agent_health_stats)
     |> assign(:assignment_breakdown, assignment_breakdown)
     |> assign(:offline_agents, offline_agents)
     |> assign(:new_token, %{agent_token: agent_token, token: token})
     |> assign(:agent_form, to_form(%{"name" => "", "is_cloud_poller" => false}))
     |> put_flash(:info, success_message)
     |> push_patch(to: ~p"/agents?modal=setup")}
  end

  defp load_cloud_pollers_if_superuser(scope) do
    if Scope.superuser?(scope), do: Agents.list_cloud_pollers(), else: []
  end

  defp calculate_device_counts(agent_tokens) do
    Map.new(agent_tokens, fn t ->
      direct = Agents.count_assigned_devices(t.id)
      total = length(Agents.list_agent_polling_targets(t.id))
      {t.id, %{direct: direct, total: total}}
    end)
  end

  defp get_success_message(is_cloud_poller) do
    if is_cloud_poller,
      do: t_equipment("Cloud poller created successfully"),
      else: t_equipment("Agent created successfully")
  end

  defp validate_and_save_global_default(nil) do
    # Clearing the default is always valid
    case Settings.set_global_default_cloud_poller(nil) do
      {:ok, _} -> {:ok, nil}
      error -> error
    end
  end

  defp validate_and_save_global_default(agent_token_id) do
    # Verify the agent exists before saving
    %Agents.AgentToken{} = Agents.get_agent_token!(agent_token_id)

    case Settings.set_global_default_cloud_poller(agent_token_id) do
      {:ok, _} -> {:ok, agent_token_id}
      error -> error
    end
  rescue
    Ecto.NoResultsError ->
      {:error, :agent_not_found}
  end

  defp handle_global_default_success(socket, agent_token_id) do
    message =
      if agent_token_id,
        do: t_equipment("Global default cloud poller set successfully"),
        else: t_equipment("Global default cloud poller cleared")

    {:noreply,
     socket
     |> assign(:global_default_cloud_poller_id, agent_token_id)
     |> assign(:selected_global_default, agent_token_id || "")
     |> put_flash(:info, message)}
  end

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

  # Handle real-time agent status updates
  # Only process events for this organization's agents, or cloud pollers if superuser
  @impl true
  def handle_info({:agent_connected, agent_token_id, organization_id}, socket) do
    {:noreply, maybe_update_agent(socket, agent_token_id, organization_id)}
  end

  @impl true
  def handle_info({:agent_disconnected, agent_token_id, organization_id}, socket) do
    {:noreply, maybe_update_agent(socket, agent_token_id, organization_id)}
  end

  @impl true
  def handle_info({:agent_heartbeat, agent_token_id, organization_id}, socket) do
    {:noreply, maybe_update_agent(socket, agent_token_id, organization_id)}
  end

  @impl true
  def handle_info({:agents_stale, _stale_agents}, socket) do
    {:noreply, reload_agent_data(socket)}
  end

  defp reload_agent_data(socket) do
    organization = socket.assigns.current_scope.organization
    agent_tokens = Agents.list_organization_agent_tokens(organization.id)

    cloud_pollers = load_cloud_pollers_if_superuser(socket.assigns.current_scope)

    equipment_counts = calculate_device_counts(agent_tokens)
    cloud_poller_counts = calculate_device_counts(cloud_pollers)

    agent_health_stats = Stats.get_organization_agent_health(organization.id)
    assignment_breakdown = Stats.get_device_assignment_breakdown(organization.id)
    offline_agents = Stats.get_offline_agents(organization.id)

    socket
    |> stream(:agent_tokens, agent_tokens, reset: true)
    |> assign(:has_agents, agent_tokens != [])
    |> stream(:cloud_pollers, cloud_pollers, reset: true)
    |> assign(:cloud_pollers_list, cloud_pollers)
    |> assign(:has_cloud_pollers, cloud_pollers != [])
    |> assign(:device_counts, equipment_counts)
    |> assign(:cloud_poller_counts, cloud_poller_counts)
    |> assign(:agent_health_stats, agent_health_stats)
    |> assign(:assignment_breakdown, assignment_breakdown)
    |> assign(:offline_agents, offline_agents)
  end

  defp maybe_update_agent(socket, agent_token_id, organization_id) do
    my_org_id = socket.assigns.current_scope.organization.id

    agent_token = Agents.get_agent_token!(agent_token_id)

    cond do
      agent_token.is_cloud_poller ->
        # Cloud pollers only visible to superusers
        if socket.assigns.is_superuser do
          socket
          |> stream_insert(:cloud_pollers, agent_token)
          |> update_health_stats()
        else
          socket
        end

      organization_id == my_org_id ->
        # Regular agent belonging to this organization
        socket
        |> stream_insert(:agent_tokens, agent_token)
        |> update_health_stats()

      true ->
        # Agent from another organization, ignore
        socket
    end
  rescue
    Ecto.NoResultsError ->
      # Agent was deleted, just refresh stats
      update_health_stats(socket)
  end

  defp update_health_stats(socket) do
    organization = socket.assigns.current_scope.organization

    agent_health_stats = Stats.get_organization_agent_health(organization.id)
    offline_agents = Stats.get_offline_agents(organization.id)

    socket
    |> assign(:agent_health_stats, agent_health_stats)
    |> assign(:offline_agents, offline_agents)
  end
end
