%% This Source Code Form is subject to the terms of the Mozilla Public
%% License, v. 2.0. If a copy of the MPL was not distributed with this
%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
%%
%% Copyright (c) 2017-2025 Broadcom. All Rights Reserved. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries.
%% @hidden
-module(ra_log_pre_init).

-behaviour(gen_server).

-include("ra.hrl").
%% API functions
-export([start_link/1]).

%% gen_server callbacks
-export([init/1,
         handle_call/3,
         handle_cast/2,
         handle_info/2,
         terminate/2,
         code_change/3]).

-record(state, {}).

-define(ETSTBL, ra_log_snapshot_state).
%%%===================================================================
%%% API functions
%%%===================================================================

start_link(System) when is_atom(System) ->
    gen_server:start_link(?MODULE, [System], []).

%%%===================================================================
%%% gen_server callbacks
%%%===================================================================

init([System]) ->
    %% ra_log:pre_init ensures that the ra_log_snapshot_state table is
    %% populated before WAL recovery begins to avoid writing unnecessary
    %% indexes to segment files.
    Regd = ra_directory:list_registered(System),
    ?INFO("ra system '~ts' running pre init for ~b registered servers",
          [System, length(Regd)]),
    _ = [begin
             try pre_init(System, UId) of
                 ok -> ok
             catch _:Err ->
                       ?ERROR("pre_init failed in system ~s for UId ~ts with name ~ts"
                              " This error may need manual intervention, Error ~p",
                              [System, UId, Name, Err]),
                       ok
             end
         end|| {Name, UId} <- Regd],
    {ok, #state{} , hibernate}.

handle_call(_Request, _From, State) ->
    Reply = ok,
    {reply, Reply, State}.

handle_cast(_Msg, State) ->
    {noreply, State}.

handle_info(_Info, State) ->
    {noreply, State}.

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%%%===================================================================
%%% Internal functions
%%%===================================================================

pre_init(System, UId) ->
    case ets:lookup(?ETSTBL, UId) of
        [{_, _, _, _}] ->
            %% already initialised
            ok;
        [] ->
            case ra_system:fetch(System) of
                undefined ->
                    {error, system_not_started};
                SysCfg ->
                    %% check if the server dir exists, if not
                    %% then just log and return instead of failing.
                    Dir = ra_env:server_data_dir(System, UId),
                    case ra_lib:is_dir(Dir) of
                        true ->
                            case ra_log:read_config(Dir) of
                                {ok, #{log_init_args := Log,
                                       machine := MachineConf}} ->
                                    Machine = case MachineConf of
                                                  {simple, Fun, S} ->
                                                      {machine, ra_machine_simple,
                                                       #{simple_fun => Fun,
                                                         initial_state => S}};
                                                  {module, Mod, Args} ->
                                                      {machine, Mod, Args}
                                              end,
                                    ok = ra_log:pre_init(Machine,
                                                         Log#{system_config =>
                                                              SysCfg}),
                                    ok;
                                {error, Err}
                                  when Err == parsing orelse
                                       Err == enoent ->
                                    %% There errors indicate corrupt or missing
                                    %% config file which cannot be recovered from
                                    %% in this case we unregister the name which
                                    %% will necessitate the member starting as
                                    %% a new member
                                    ?ERROR("pre_init failed to read config file "
                                           "for UId '~ts', Err ~p, recovery cannot "
                                           "complete with a corrupt config file "
                                           "unregistering name to start as new member",
                                           [UId, Err]),
                                    ?CATCH(ra_directory:unregister_name(System, UId)),
                                    ok;
                                {error, Err} ->
                                    ?ERROR("pre_init failed to read config file "
                                           "for UId '~ts', Err ~p ",
                                           [UId, Err]),
                                    ok
                            end;
                        false ->
                            ?INFO("pre_init UId '~ts' is registered but no data
                                  directory was found, removing from ra directory",
                                  [UId]),
                            ?CATCH(ra_directory:unregister_name(System, UId)),
                            ok
                    end
            end
    end.

