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

  use Application

  require Logger

  @impl true
  def start(_type, _args) do
    # Initialize deployment timestamp
    _ = Aprsme.Release.init()

    # Run migrations on startup
    migrate()

    children = [
      # Start the Telemetry supervisor
      AprsmeWeb.Telemetry,
      # Start the Prometheus metrics exporter
      Aprsme.PromEx,
      # Start the Ecto repository
      Aprsme.Repo,
      # Start the PubSub system
      pubsub_config(),
      Aprsme.DeploymentNotifier,
      # Start Redis-based rate limiter and caches (only if Redis is available)
      # Start circuit breaker
      Aprsme.CircuitBreaker,
      # Start regex cache for performance
      Aprsme.RegexCache,
      # Start device cache manager
      Aprsme.DeviceCache,
      # Start broadcast task supervisor for async operations
      Aprsme.BroadcastTaskSupervisor,
      # Start spatial PubSub for viewport-based filtering
      Aprsme.SpatialPubSub,
      # Manage daily partitions for the packets table
      Aprsme.PartitionManager,
      # Start the Endpoint (http/https)
      AprsmeWeb.Endpoint,
      # Start cleanup scheduler for periodic packet cleanup
      Aprsme.CleanupScheduler,
      Aprsme.PostgresNotifier,
      # Start the packet processing pipeline
      Aprsme.PacketPipelineSupervisor
    ]

    # Skip partition manager and packet pipeline in test to avoid Sandbox ownership errors
    children =
      if Application.get_env(:aprsme, :env) == :test do
        children
        |> List.delete(Aprsme.PartitionManager)
        |> List.delete(Aprsme.PacketPipelineSupervisor)
      else
        children
      end

    children = children ++ ets_children()

    # Add shutdown handlers at the end, after everything else is started
    children =
      children ++
        [
          Aprsme.SignalHandler,
          Aprsme.ShutdownHandler
        ]

    children = maybe_add_cluster_components(children)
    children = maybe_add_is_supervisor(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)

    # Attach error notification telemetry handler
    _ = Aprsme.ErrorNotifier.attach()

    # 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
        Aprsme.BroadcastTaskSupervisor.async_execute(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)
    cluster_enabled = Application.get_env(:aprsme, :cluster_enabled, false)

    # In cluster mode, prefer init containers or manual migration
    # to avoid race conditions between nodes
    if auto_migrate and not cluster_enabled do
      do_migrate(true)
    else
      if cluster_enabled do
        Logger.info("Skipping auto-migration in cluster mode")
      else
        Logger.info("Auto-migration disabled")
      end
    end

    # Gettext translations are automatically compiled during Mix compilation
  end

  defp maybe_add_cluster_components(children) do
    if Application.get_env(:aprsme, :cluster_enabled, false) do
      topologies = Application.get_env(:libcluster, :topologies, [])

      # Packet receiver for distributed packets on non-leader nodes
      cluster_children =
        if topologies == [] do
          []
        else
          [
            {Cluster.Supervisor, [topologies, [name: Aprsme.ClusterSupervisor]]},
            Aprsme.DynamicSupervisor,
            Aprsme.Cluster.LeaderElection,
            Aprsme.Cluster.ConnectionManager,
            Aprsme.Cluster.PacketDistributor,
            Aprsme.ConnectionMonitor
          ]
        end

      children ++ cluster_children
    else
      children
    end
  end

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

    # Only add Is.IsSupervisor directly if clustering is disabled
    if env in [:prod, :dev] and not disable_connection and not cluster_enabled do
      children ++ [Aprsme.Is.IsSupervisor]
    else
      children
    end
  end

  defp do_migrate(true) do
    Logger.info("Running database migrations...")
    Aprsme.Release.migrate()
    Logger.info("Database migrations completed")
  end

  defp pubsub_config do
    {Phoenix.PubSub, name: Aprsme.PubSub}
  end

  defp ets_children do
    Logger.info("Starting ETS-based caching and rate limiting")

    # Create public ETS tables so callers can use concurrent reads and writes directly.
    _ = :ets.new(:query_cache, [:set, :public, :named_table, read_concurrency: true, write_concurrency: true])
    _ = :ets.new(:device_cache, [:set, :public, :named_table, read_concurrency: true, write_concurrency: true])
    _ = :ets.new(:symbol_cache, [:set, :public, :named_table, read_concurrency: true, write_concurrency: true])
    _ = :ets.new(:weather_cache, [:set, :public, :named_table, read_concurrency: true, write_concurrency: true])
    _ = :ets.new(:aprsme, [:set, :public, :named_table, read_concurrency: true, write_concurrency: true])
    _ = :ets.insert(:aprsme, {:message_number, 0})

    [
      # ETS-based rate limiter
      Aprsme.RateLimiter
    ]
  end
end
