defmodule NervesHub.Application do
  use Application

  require Logger

  def start(_type, _args) do
    case System.cmd("fwup", ["--version"], env: []) do
      {_, 0} ->
        Logger.debug("fwup was found")

      _ ->
        raise "fwup could not be found in the $PATH. This is a requirement of NervesHubWeb and cannot start otherwise"
    end

    setup_open_telemetry()
    _ = setup_logging()

    children =
      [{Finch, name: Swoosh.Finch}] ++
        ecto_migrations() ++
        NervesHub.StatsdMetricsReporter.config() ++
        [
          NervesHub.MetricsPoller.child_spec(),
          NervesHub.RateLimit
        ] ++
        ecto_repos() ++
        [
          {Phoenix.PubSub, name: NervesHub.PubSub},
          {Cluster.Supervisor, [libcluster_topology()]},
          {Task.Supervisor, name: NervesHub.TaskSupervisor},
          {Oban, oban_opts()},
          NervesHubWeb.Presence,
          {NervesHub.RateLimit.LogLines, [clean_period: to_timeout(minute: 5), key_older_than: to_timeout(hour: 1)]},
          {PartitionSupervisor, child_spec: Task.Supervisor, name: NervesHub.AnalyticsEventsProcessing}
        ] ++
        deployments_orchestrator(deploy_env()) ++
        endpoints(deploy_env())

    opts = [strategy: :one_for_one, name: NervesHub.Supervisor]
    Supervisor.start_link(children, opts)
  end

  defp setup_logging() do
    :ok =
      :logger.add_handler(:sentry_handler, Sentry.LoggerHandler, %{
        config: %{metadata: [:file, :line]}
      })

    :ok =
      :logger.add_primary_filter(:filter_ssl_handshake, {&NervesHub.Logger.ssl_log_filter/2, []})

    NervesHub.Logger.attach()
  end

  defp setup_open_telemetry() do
    if System.get_env("ECTO_IPV6") do
      :ok = :httpc.set_option(:ipfamily, :inet6fb4)
    end

    :ok = NervesHub.Telemetry.Customizations.setup()

    :ok = OpentelemetryBandit.setup()
    :ok = OpentelemetryPhoenix.setup(adapter: :bandit)
    :ok = OpentelemetryOban.setup(trace: [:jobs])

    :ok =
      NervesHub.Repo.config()
      |> Keyword.fetch!(:telemetry_prefix)
      |> OpentelemetryEcto.setup(db_statement: :enabled)

    :ok
  end

  def config_change(changed, _new, removed) do
    NervesHubWeb.Endpoint.config_change(changed, removed)
    :ok
  end

  defp libcluster_topology() do
    repo_config =
      NervesHub.Repo.config()
      |> Keyword.take([:hostname, :username, :password, :database, :port, :ssl])
      |> Keyword.put(:parameters, [])
      |> Keyword.put(:channel_name, "nerves_hub_clustering")

    [
      app: [
        strategy: LibclusterPostgres.Strategy,
        config: repo_config
      ]
    ]
  end

  defp oban_opts() do
    config = Application.fetch_env!(:nerves_hub, Oban)

    case Application.get_env(:nerves_hub, :app) do
      "device" ->
        Keyword.put(config, :queues, [])

      _ ->
        config
    end
  end

  defp ecto_migrations() do
    [
      Supervisor.child_spec(
        {Ecto.Migrator,
         repos: [NervesHub.Repo], skip: Application.get_env(:nerves_hub, :database_auto_migrator) != true},
        id: :repo_migrator
      ),
      Supervisor.child_spec(
        {Ecto.Migrator,
         repos: [NervesHub.AnalyticsRepo], skip: Application.get_env(:nerves_hub, :analytics_auto_migrator) != true},
        id: :analytics_repo_migrator
      )
    ]
  end

  defp ecto_repos() do
    [NervesHub.Repo, NervesHub.ObanRepo] ++
      if Application.get_env(:nerves_hub, :analytics_enabled) do
        [NervesHub.AnalyticsRepo]
      else
        []
      end
  end

  defp deployments_orchestrator("test"), do: []

  # Only run the `ProcessHub` supervisor on the `web` or `all` nodes only.
  defp deployments_orchestrator(_) do
    case Application.get_env(:nerves_hub, :app) do
      "device" ->
        []

      _ ->
        [
          ProcessHub.child_spec(%ProcessHub{hub_id: :deployment_orchestrators}),
          NervesHub.ManagedDeployments.Distributed.OrchestratorRegistration
        ]
    end
  end

  defp endpoints("test") do
    [
      NervesHubWeb.DeviceEndpoint,
      NervesHubWeb.Endpoint
    ]
  end

  defp endpoints(_) do
    case Application.get_env(:nerves_hub, :app) do
      "all" ->
        [
          NervesHubWeb.DeviceEndpoint,
          NervesHubWeb.Endpoint
        ]

      "device" ->
        [NervesHubWeb.DeviceEndpoint]

      "web" ->
        [NervesHubWeb.Endpoint]
    end
  end

  defp deploy_env(), do: Application.get_env(:nerves_hub, :deploy_env)
end
