defmodule Towerops.Search do
  @moduledoc """
  Global search across devices, sites, accounts, and alerts.
  """

  import Ecto.Query
  alias Towerops.Repo
  alias Towerops.Devices.Device
  alias Towerops.Sites.Site
  alias Towerops.Gaiia.Account
  alias Towerops.Alerts.Alert

  @limit 5

  @doc """
  Searches across multiple entity types for the given organization.
  Returns a map of categorized results.
  """
  def search(_organization_id, query) when byte_size(query) < 2, do: %{}

  def search(organization_id, query) do
    pattern = "%#{sanitize(query)}%"

    %{
      devices: search_devices(organization_id, pattern),
      sites: search_sites(organization_id, pattern),
      accounts: search_accounts(organization_id, pattern),
      alerts: search_alerts(organization_id, pattern)
    }
    |> Enum.reject(fn {_k, v} -> v == [] end)
    |> Map.new()
  end

  defp search_devices(organization_id, pattern) do
    Device
    |> where([d], d.organization_id == ^organization_id)
    |> where([d], ilike(d.name, ^pattern) or ilike(d.ip_address, ^pattern))
    |> order_by([d], d.name)
    |> limit(@limit)
    |> select([d], %{id: d.id, name: d.name, ip_address: d.ip_address})
    |> Repo.all()
    |> Enum.map(fn d ->
      %{type: :device, label: d.name, sublabel: d.ip_address, url: "/devices/#{d.id}"}
    end)
  end

  defp search_sites(organization_id, pattern) do
    Site
    |> where([s], s.organization_id == ^organization_id)
    |> where([s], ilike(s.name, ^pattern))
    |> order_by([s], s.name)
    |> limit(@limit)
    |> select([s], %{id: s.id, name: s.name, location: s.location})
    |> Repo.all()
    |> Enum.map(fn s ->
      %{type: :site, label: s.name, sublabel: s.location, url: "/sites/#{s.id}"}
    end)
  end

  defp search_accounts(organization_id, pattern) do
    Account
    |> where([a], a.organization_id == ^organization_id)
    |> where([a], ilike(a.name, ^pattern) or ilike(a.readable_id, ^pattern))
    |> order_by([a], a.name)
    |> limit(@limit)
    |> select([a], %{id: a.id, name: a.name, readable_id: a.readable_id})
    |> Repo.all()
    |> Enum.map(fn a ->
      %{type: :account, label: a.name, sublabel: a.readable_id, url: "/trace?account_id=#{a.id}"}
    end)
  end

  defp search_alerts(organization_id, pattern) do
    Alert
    |> join(:inner, [a], d in Device, on: a.device_id == d.id)
    |> where([a, d], d.organization_id == ^organization_id)
    |> where([a, _d], ilike(a.message, ^pattern))
    |> where([a, _d], is_nil(a.resolved_at))
    |> order_by([a, _d], [desc: a.triggered_at])
    |> limit(@limit)
    |> select([a, d], %{id: a.id, message: a.message, device_name: d.name})
    |> Repo.all()
    |> Enum.map(fn a ->
      %{type: :alert, label: a.message || "Alert", sublabel: a.device_name, url: "/alerts"}
    end)
  end

  defp sanitize(query) do
    query
    |> String.replace("%", "\\%")
    |> String.replace("_", "\\_")
  end
end
