defmodule Towerops.Sites do
  @moduledoc """
  The Sites context.
  """

  import Ecto.Query

  alias Towerops.Agents.AgentAssignment
  alias Towerops.Devices.Device
  alias Towerops.Repo
  alias Towerops.Sites.Site

  @doc """
  Returns the list of sites for an organization.
  """
  def list_organization_sites(organization_id) do
    Repo.all(
      from(s in Site, where: s.organization_id == ^organization_id, order_by: [asc: s.name], preload: [:parent_site])
    )
  end

  @doc """
  Returns the count of sites for an organization.
  """
  def count_organization_sites(organization_id) do
    Repo.aggregate(
      from(s in Site, where: s.organization_id == ^organization_id),
      :count
    )
  end

  @doc """
  Returns the list of root sites (sites without a parent) for an organization.
  """
  def list_root_sites(organization_id) do
    Repo.all(
      from(s in Site,
        where: s.organization_id == ^organization_id,
        where: is_nil(s.parent_site_id),
        order_by: [asc: s.name]
      )
    )
  end

  @doc """
  Returns the list of child sites for a parent site.
  """
  def list_child_sites(parent_site_id) do
    Repo.all(from(s in Site, where: s.parent_site_id == ^parent_site_id, order_by: [asc: s.name]))
  end

  @doc """
  Gets a single site.
  """
  def get_site!(id) do
    Site
    |> Repo.get!(id)
    |> Repo.preload([:parent_site, :child_sites, :device])
  end

  @doc """
  Gets a single site belonging to an organization.
  """
  def get_organization_site!(organization_id, site_id) do
    Repo.one!(
      from(s in Site,
        where: s.id == ^site_id,
        where: s.organization_id == ^organization_id,
        preload: [:parent_site, :child_sites, :device]
      )
    )
  end

  @doc """
  Creates a site.
  """
  def create_site(attrs) do
    %Site{}
    |> Site.changeset(attrs)
    |> Repo.insert()
  end

  @doc """
  Updates a site.
  """
  def update_site(%Site{} = site, attrs) do
    site
    |> Site.changeset(attrs)
    |> Repo.update()
  end

  @doc """
  Deletes a site.
  """
  def delete_site(%Site{} = site) do
    Repo.delete(site)
  end

  @doc """
  Returns an `%Ecto.Changeset{}` for tracking site changes.
  """
  def change_site(%Site{} = site, attrs \\ %{}) do
    Site.changeset(site, attrs)
  end

  @doc """
  Builds a hierarchical tree structure of sites.
  """
  def build_site_tree(organization_id) do
    sites = list_organization_sites(organization_id)
    root_sites = Enum.filter(sites, &is_nil(&1.parent_site_id))

    Enum.map(root_sites, fn root ->
      build_tree_node(root, sites)
    end)
  end

  defp build_tree_node(site, all_sites) do
    children =
      all_sites
      |> Enum.filter(&(&1.parent_site_id == site.id))
      |> Enum.map(&build_tree_node(&1, all_sites))

    %{site: site, children: children}
  end

  ## Bulk Configuration Updates

  @doc """
  Applies site's SNMP configuration to all devices at the site.

  This updates all devices records to use the site's SNMP version and community string,
  overwriting any existing device-level configurations.

  Returns the number of device records updated.
  """
  def apply_snmp_config_to_all_equipment(site_id) do
    site = get_site!(site_id)

    Repo.update_all(from(e in Device, where: e.site_id == ^site_id),
      set: [
        snmp_version: site.snmp_version,
        snmp_community: site.snmp_community,
        updated_at: DateTime.truncate(DateTime.utc_now(), :second)
      ]
    )
  end

  @doc """
  Applies site's agent to all devices at the site.

  This creates/updates agent assignments for all devices at the site to use the site's
  agent, overwriting any existing device-level assignments.

  Returns the number of device records assigned.
  """
  def apply_agent_to_all_equipment(site_id) do
    site = Repo.get!(Site, site_id)

    # device IDs for this site
    device_ids = Repo.all(from(e in Device, where: e.site_id == ^site_id, select: e.id))

    if site.agent_token_id do
      # Delete existing assignments
      Repo.delete_all(from(a in AgentAssignment, where: a.device_id in ^device_ids))

      # Create new assignments
      now = DateTime.truncate(DateTime.utc_now(), :second)

      assignments =
        Enum.map(device_ids, fn device_id ->
          %{
            device_id: device_id,
            agent_token_id: site.agent_token_id,
            inserted_at: now,
            updated_at: now
          }
        end)

      {count, _} = Repo.insert_all(AgentAssignment, assignments)
      {count, nil}
    else
      # No agent set, delete all assignments (they will inherit from org or use cloud)
      Repo.delete_all(from(a in AgentAssignment, where: a.device_id in ^device_ids))
    end
  end
end
