defmodule Plausible.DataMigration.NumericIDs do
  @moduledoc """
  Numeric IDs migration, SQL files available at:
  priv/data_migrations/NumericIDs/sql
  """
  use Plausible.DataMigration, dir: "NumericIDs"

  import Ecto.Query

  defmodule DomainsLookup do
    @moduledoc false
    use Ecto.Schema

    @primary_key false
    schema "domains_lookup" do
      field :site_id, Ch, type: "UInt64"
      field :domain, :string
    end
  end

  @table_settings "SETTINGS index_granularity = 8192"

  def run(opts \\ []) do
    interactive? = Keyword.get(opts, :interactive?, true)

    db_url =
      System.get_env(
        "NUMERIC_IDS_MIGRATION_DB_URL",
        Application.get_env(:plausible, Plausible.IngestRepo)[:url]
      )

    max_threads =
      "NUMERIC_IDS_MIGRATION_MAX_THREADS" |> System.get_env("16") |> String.to_integer()

    table_settings =
      Keyword.get(opts, :table_settings) || System.get_env("NUMERIC_IDS_TABLE_SETTINGS") ||
        @table_settings

    start_from =
      Keyword.get(opts, :start_from) || System.get_env("NUMERIC_IDS_PARTITION_START_FROM")

    stop_at = Keyword.get(opts, :stop_at) || System.get_env("NUMERIC_IDS_PARTITION_STOP_AT")

    @repo.start(db_url, max_threads)

    cluster? =
      case run_sql("check-replicas") do
        {:ok, %{num_rows: 0}} -> false
        {:ok, %{num_rows: 1}} -> true
      end

    {:ok, %{rows: partitions}} =
      run_sql("list-partitions", start_from: start_from, stop_at: stop_at)

    partitions = Enum.map(partitions, fn [part] -> part end)

    start_from = start_from || List.first(partitions)

    IO.puts("""
    Got the following migration settings:

      - max_threads: #{max_threads}
      - table_settings: #{table_settings}
      - db url: #{db_url}
      - cluster?: #{cluster?}
      - partitions to do: #{inspect(partitions, pretty: true, limit: :infinity, width: 80)}
      - start from: #{start_from}
      - stop at: #{stop_at}
    """)

    run_sql_fn =
      if interactive? do
        &run_sql_confirm/2
      else
        &run_sql/2
      end

    confirm_fn =
      if interactive? do
        &confirm/2
      else
        fn _, run_fn ->
          run_fn.()
        end
      end

    drop_v2_extra_opts = fn table ->
      case @repo.query("select count(*) from {$0:Identifier}", [table]) do
        {:ok, %{rows: [[count]]}} when count > 0 ->
          [
            prompt_message: "The table contains #{count} rows. Execute?",
            prompt_default_choice: :no
          ]

        {:ok, _} ->
          [prompt_default_choice: :no]

        {:error, _} ->
          []
      end
    end

    {:ok, _} =
      run_sql_fn.("drop-events-v2", [cluster?: cluster?] ++ drop_v2_extra_opts.("events_v2"))

    {:ok, _} =
      run_sql_fn.("drop-sessions-v2", [cluster?: cluster?] ++ drop_v2_extra_opts.("sessions_v2"))

    {:ok, _} = run_sql_fn.("drop-tmp-events-v2", [])
    {:ok, _} = run_sql_fn.("drop-tmp-sessions-v2", [])
    {:ok, _} = run_sql_fn.("drop-domains-lookup", [])

    {:ok, _} = run_sql_fn.("create-events-v2", table_settings: table_settings, cluster?: cluster?)

    {:ok, _} =
      run_sql_fn.("create-sessions-v2", table_settings: table_settings, cluster?: cluster?)

    {:ok, _} = run_sql_fn.("create-tmp-events-v2", table_settings: table_settings)
    {:ok, _} = run_sql_fn.("create-tmp-sessions-v2", table_settings: table_settings)

    case run_sql_fn.("create-domains-lookup", table_settings: table_settings) do
      {:ok, _} ->
        confirm_fn.("Populate domains-lookup with postgres sites", fn ->
          mappings =
            Plausible.Site
            |> select([s], %{site_id: s.id, domain: s.domain})
            |> Plausible.Repo.all()

          @repo.insert_all(DomainsLookup, mappings)
        end)

      _ ->
        :ignore
    end

    confirm_fn.("Start migration? (starting from partition: #{start_from})", fn ->
      IO.puts("start.. #{DateTime.utc_now()}")

      for part <- partitions do
        part_start = System.monotonic_time()

        confirm_fn.("Run partition: #{part}?", fn ->
          {:ok, _} = run_sql("insert-into-tmp-events-v2", partition: part)
          {:ok, _} = run_sql("attach-tmp-events-v2", partition: part)
          {:ok, _} = run_sql("truncate-tmp-events-v2", [])
          {:ok, _} = run_sql("insert-into-tmp-sessions-v2", partition: part)
          {:ok, _} = run_sql("attach-tmp-sessions-v2", partition: part)
          {:ok, _} = run_sql("truncate-tmp-sessions-v2", [])
        end)

        part_end = System.monotonic_time()

        IO.puts(
          "#{part} took #{System.convert_time_unit(part_end - part_start, :native, :second)} seconds"
        )
      end

      IO.puts("end.. #{DateTime.utc_now()}")
    end)
  end
end
