defmodule Aprsme.Application do
  # See https://hexdocs.pm/elixir/Application.html
  # for more information on OTP Applications
  @moduledoc false

  use Application

  # Configure Oban for background jobs

  @impl true
  def start(_type, _args) do
    # Run migrations on startup
    migrate()

    :logger.add_handler(:my_sentry_handler, Sentry.LoggerHandler, %{
      config: %{metadata: [:file, :line]}
    })

    topologies = Application.get_env(:libcluster, :topologies) || []

    children = [
      # Start the Telemetry supervisor
      AprsmeWeb.Telemetry,
      # Start the Ecto repository
      Aprsme.Repo,
      # Start the PubSub system
      {Phoenix.PubSub, name: Aprsme.PubSub},
      # Start cache systems
      %{id: :query_cache, start: {Cachex, :start_link, [:query_cache, [limit: 10_000]]}},
      %{id: :device_cache, start: {Cachex, :start_link, [:device_cache, [limit: 5_000]]}},
      %{id: :symbol_cache, start: {Cachex, :start_link, [:symbol_cache, [limit: 1_000]]}},
      # Start circuit breaker
      Aprsme.CircuitBreaker,

      # Start the Endpoint (http/https)
      AprsmeWeb.Endpoint,
      # Start a worker by calling: Aprsme.Worker.start_link(arg)
      # {Aprsme.Worker, arg}
      {Registry, keys: :duplicate, name: Registry.PubSub, partitions: System.schedulers_online()},
      {Cluster.Supervisor, [topologies, [name: Aprsme.ClusterSupervisor]]},
      # Start Oban for background jobs
      {Oban, :aprsme |> Application.get_env(Oban, []) |> Keyword.put(:queues, default: 10, maintenance: 2)},
      Aprsme.Presence,
      Aprsme.PostgresNotifier,
      # Start the packet processing pipeline
      Aprsme.PacketPipelineSupervisor,
      # Start the packet pipeline setup
      Aprsme.PacketPipelineSetup
    ]

    children = maybe_add_is_supervisor(children, Application.get_env(:aprsme, :env))
    children = maybe_add_aprs_connection(children, Application.get_env(:aprsme, :env))

    # See https://hexdocs.pm/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: Aprsme.Supervisor]
    {:ok, sup} = Supervisor.start_link(children, opts)

    # Now that the Repo is started, run the refresh in a background task
    # Skip in test environment to avoid DBConnection.OwnershipError
    env = Application.get_env(:aprsme, :env)

    if env != :test do
      Task.start(fn -> Aprsme.DeviceIdentification.maybe_refresh_devices() end)
    end

    {:ok, sup}
  end

  # Tell Phoenix to update the endpoint configuration
  # whenever the application is updated.
  @impl true
  def config_change(changed, _new, removed) do
    AprsmeWeb.Endpoint.config_change(changed, removed)
    :ok
  end

  defp migrate do
    auto_migrate = Application.get_env(:aprsme, :auto_migrate, true)
    do_migrate(auto_migrate)

    # Gettext translations are automatically compiled during Mix compilation
  rescue
    error ->
      require Logger

      Logger.error("Failed to run migrations: #{inspect(error)}")
      # Don't crash the application, just log the error
      :ok
  end

  defp maybe_add_is_supervisor(children, env) do
    disable_connection = Application.get_env(:aprsme, :disable_aprs_connection, false)

    if env in [:prod, :dev] and not disable_connection do
      children ++ [Aprsme.Is.IsSupervisor]
    else
      children
    end
  end

  defp maybe_add_aprs_connection(children, _env) do
    disable_connection = Application.get_env(:aprsme, :disable_aprs_connection, false)

    if disable_connection do
      children
    else
      children ++ [Aprsme.AprsIsConnection]
    end
  end

  defp do_migrate(true) do
    require Logger

    Logger.info("Running database migrations...")
    Aprsme.Release.migrate()
    Logger.info("Database migrations completed")
  end

  defp do_migrate(false) do
    require Logger

    Logger.info("Automatic migrations disabled")
  end
end
