defmodule Aprsme.Release do
  @moduledoc """
  Used for executing DB release tasks when run in production without Mix
  installed.
  """
  alias Ecto.Adapters.SQL

  require Logger

  @app :aprsme

  def migrate do
    # Gettext translations are automatically compiled during Mix compilation

    # Skip database creation when using PgBouncer
    # The database should already exist
    if System.get_env("SKIP_DB_CREATE") != "true" do
      create_database()
    end

    # Run migrations with distributed lock
    cluster_enabled = Application.get_env(:aprsme, :cluster_enabled, false)

    # Configure longer timeouts for migration operations
    # 2 hours default
    migration_timeout = String.to_integer(System.get_env("MIGRATION_TIMEOUT", "7200000"))

    if cluster_enabled do
      Logger.info("Running migrations with distributed lock...")
      # Ensure repo is started for advisory lock with extended timeout
      repo_config =
        Aprsme.Repo.config()
        |> Keyword.put(:timeout, migration_timeout)
        |> Keyword.put(:pool_timeout, 60_000)

      {:ok, _} = Aprsme.Repo.start_link(repo_config)

      Aprsme.MigrationLock.with_lock(Aprsme.Repo, fn ->
        run_migrations_with_timeout(migration_timeout)
      end)
    else
      Logger.info("Running migrations without lock (single node)...")
      run_migrations_with_timeout(migration_timeout)
    end
  end

  defp create_database do
    case Aprsme.Repo.__adapter__().storage_up(Aprsme.Repo.config()) do
      :ok ->
        Logger.info("Database created successfully")

      {:error, :already_up} ->
        Logger.info("Database already exists")

      {:error, error} ->
        Logger.error("Failed to create database: #{inspect(error)}")
        raise "Database creation failed: #{inspect(error)}"
    end
  end

  def rollback(repo, version) do
    _ = load_app()
    {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))
  end

  defp load_app do
    Application.load(@app)
  end

  @doc """
  Initialize release-specific configuration.
  This is called during application startup.
  """
  def init do
    # Check if deployment timestamp is provided via environment variable
    # This allows k8s deployments to set a persistent timestamp
    deployed_at =
      case System.get_env("DEPLOYED_AT") do
        nil ->
          # Fallback to current time for local development or non-k8s deployments
          DateTime.utc_now()

        timestamp_str ->
          # Parse the ISO8601 timestamp from environment variable
          case DateTime.from_iso8601(timestamp_str) do
            {:ok, datetime, _offset} ->
              datetime

            {:error, _} ->
              Logger.warning("Invalid DEPLOYED_AT timestamp: #{timestamp_str}, using current time")
              DateTime.utc_now()
          end
      end

    # Store in application config
    Application.put_env(:aprsme, :deployed_at, deployed_at)

    deployed_at
  end

  @doc """
  Get the deployment timestamp.
  """
  def deployed_at do
    case Application.get_env(:aprsme, :deployed_at) do
      nil ->
        # If not initialized yet, initialize now
        timestamp = DateTime.utc_now()
        Application.put_env(:aprsme, :deployed_at, timestamp)
        timestamp

      timestamp ->
        timestamp
    end
  end

  defp run_migrations_with_timeout(timeout) do
    Logger.info("Running migrations with timeout: #{timeout}ms")

    # Run with extended timeout configuration
    _repo_config =
      Aprsme.Repo.config()
      |> Keyword.put(:timeout, timeout)
      |> Keyword.put(:pool_timeout, 60_000)

    {:ok, _, _} =
      Ecto.Migrator.with_repo(
        Aprsme.Repo,
        fn repo ->
          timeout_seconds = div(timeout, 1000)
          _ = SQL.query!(repo, "SET statement_timeout = '#{timeout_seconds}s'", [])
          Ecto.Migrator.run(repo, :up, all: true)
        end,
        timeout: timeout
      )
  end
end
