defmodule Towerops.Application do
  @moduledoc """
  OTP Application module for Towerops.

  Supervises the Repo, PubSub, Telemetry, Endpoint, and monitoring workers.
  """

  use Application

  # Capture the build timestamp at compile time (fallback for development)
  @build_timestamp DateTime.utc_now()

  @impl true
  def start(_type, _args) do
    # Run migrations on startup (Ecto handles locking for concurrent runs)
    _ =
      if Application.get_env(:towerops, Towerops.Repo)[:database] != "towerops_test" do
        Towerops.Release.migrate()
      end

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

    children =
      [
        {Cluster.Supervisor, [topologies, [name: Towerops.ClusterSupervisor]]},
        ToweropsWeb.Telemetry,
        Towerops.Repo,
        {DNSCluster, query: Application.get_env(:towerops, :dns_cluster_query) || :ignore},
        pubsub_spec(),
        # Start event logger (subscribes to PubSub)
        Towerops.Devices.EventLogger,
        # Start monitoring supervisor
        Towerops.Monitoring.Supervisor
        # Start a worker by calling: Towerops.Worker.start_link(arg)
        # {Towerops.Worker, arg},
      ] ++
        exq_workers() ++
        [
          # Start to serve requests, typically the last entry
          ToweropsWeb.Endpoint
        ]

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

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

  # Configure Exq workers - don't start in test environment
  defp exq_workers do
    if Application.get_env(:towerops, :env) == :test do
      []
    else
      [
        # Wrap Exq in a supervisor with restart strategy to handle Redis connection failures
        %{
          id: Exq,
          start: {Exq, :start_link, [exq_config()]},
          restart: :permanent,
          type: :supervisor
        }
      ]
    end
  end

  defp exq_config do
    redis_config = Application.get_env(:towerops, :redis, [])

    [
      name: Exq,
      host: Keyword.get(redis_config, :host, "localhost"),
      port: Keyword.get(redis_config, :port, 6379),
      namespace: "exq",
      concurrency: 10,
      queues: ["default", "discovery", "polling", "monitoring", "maintenance"],
      # Add connection retries for Redis
      max_retries: 25,
      # Start with a small backoff that increases
      backoff: 100
    ]
  end

  # Returns PubSub spec - uses Redis in production/clustered environments,
  # falls back to default PG2 adapter for Mix tasks and development
  defp pubsub_spec do
    redis_config = Application.get_env(:towerops, :redis, [])
    node_name = node()

    # Use Redis adapter only when:
    # 1. Redis config exists with host
    # 2. Node has a name (not nonode@nohost)
    use_redis? =
      Keyword.has_key?(redis_config, :host) &&
        node_name != :nonode@nohost

    if use_redis? do
      {Phoenix.PubSub, name: Towerops.PubSub, adapter: Phoenix.PubSub.Redis, redis_config: redis_pubsub_config()}
    else
      # Default PG2 adapter for Mix tasks and development
      {Phoenix.PubSub, name: Towerops.PubSub}
    end
  end

  defp redis_pubsub_config do
    redis_config = Application.get_env(:towerops, :redis, [])

    [
      host: Keyword.get(redis_config, :host, "localhost"),
      port: Keyword.get(redis_config, :port, 6379),
      node_name: node()
    ]
  end

  @doc """
  Returns the deployment timestamp.

  In production (Kubernetes), this reads from the DEPLOY_TIMESTAMP environment
  variable which is automatically set by GitLab CI during deployment. Falls back
  to compile time in development.

  The timestamp is set by GitLab CI using:
  ```bash
  kubectl set env deployment/towerops DEPLOY_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") -n towerops
  ```

  This ensures all pods in the deployment show the same deployment time, regardless
  of when individual pods were created or restarted.
  """
  @spec build_timestamp() :: DateTime.t()
  def build_timestamp do
    case System.get_env("DEPLOY_TIMESTAMP") do
      nil ->
        # Development/fallback: use compile time
        @build_timestamp

      timestamp_string ->
        # Production: parse from environment variable
        case DateTime.from_iso8601(timestamp_string) do
          {:ok, datetime, _offset} -> datetime
          {:error, _} -> @build_timestamp
        end
    end
  end
end
