defmodule Towerops.Profiles do
  @moduledoc """
  Context for managing device profiles.
  Profiles are stored in the database and can be updated without deployment.
  """

  import Ecto.Query

  alias Towerops.Profiles.DeviceOid
  alias Towerops.Profiles.DeviceProfile
  alias Towerops.Profiles.SensorOid
  alias Towerops.Repo

  @doc """
  Lists all enabled profiles ordered by priority.
  """
  def list_profiles do
    DeviceProfile
    |> where([p], p.enabled == true)
    |> order_by([p], asc: p.priority)
    |> Repo.all()
  end

  @doc """
  Gets a profile by name with associations loaded.
  """
  def get_profile(name) do
    DeviceProfile
    |> where([p], p.name == ^name)
    |> preload([:device_oids, :sensor_oids])
    |> Repo.one()
  end

  @doc """
  Matches a profile based on sysDescr and sysObjectID.
  Returns the first matching profile based on priority.
  """
  def match_profile(system_info) do
    sys_descr = Map.get(system_info, :sys_descr, "")
    sys_object_id = Map.get(system_info, :sys_object_id, "")

    profiles = list_profiles()

    Enum.find(profiles, fn profile ->
      matches_detection_pattern?(profile, sys_descr) ||
        matches_detection_oid?(profile, sys_object_id)
    end)
  end

  @doc """
  Creates a new profile.
  """
  def create_profile(attrs) do
    %DeviceProfile{}
    |> DeviceProfile.changeset(attrs)
    |> Repo.insert()
  end

  @doc """
  Updates a profile.
  """
  def update_profile(%DeviceProfile{} = profile, attrs) do
    profile
    |> DeviceProfile.changeset(attrs)
    |> Repo.update()
  end

  @doc """
  Deletes a profile.
  """
  def delete_profile(%DeviceProfile{} = profile) do
    Repo.delete(profile)
  end

  @doc """
  Adds a device OID to a profile.
  """
  def add_device_oid(profile_id, field, mib_name) do
    %DeviceOid{}
    |> DeviceOid.changeset(%{
      profile_id: profile_id,
      field: field,
      mib_name: mib_name
    })
    |> Repo.insert()
  end

  @doc """
  Adds a sensor OID to a profile.
  """
  def add_sensor_oid(profile_id, attrs) do
    %SensorOid{}
    |> SensorOid.changeset(Map.put(attrs, :profile_id, profile_id))
    |> Repo.insert()
  end

  # Private functions

  defp matches_detection_pattern?(%{detection_pattern: nil}, _sys_descr), do: false

  defp matches_detection_pattern?(%{detection_pattern: pattern}, sys_descr) do
    case Regex.compile(pattern) do
      {:ok, regex} -> Regex.match?(regex, sys_descr)
      {:error, _} -> false
    end
  end

  defp matches_detection_oid?(%{detection_oid: nil}, _sys_object_id), do: false

  defp matches_detection_oid?(%{detection_oid: pattern}, sys_object_id) do
    String.starts_with?(sys_object_id, pattern)
  end
end
