%% 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) 2007-2023 VMware, Inc. or its affiliates.  All rights reserved.
%%

-module(rabbit_error_logger_handler).

-behaviour(gen_event).

%% API
-export([start_link/0, add_handler/0]).

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

-define(SERVER, ?MODULE).

-record(state, {report = []}).

%%%===================================================================
%%% API
%%%===================================================================

%%--------------------------------------------------------------------
%% @doc
%% Creates an event manager
%%
%% @spec start_link() -> {ok, Pid} | {error, Error}
%% @end
%%--------------------------------------------------------------------
start_link() ->
    gen_event:start_link({local, ?SERVER}).

%%--------------------------------------------------------------------
%% @doc
%% Adds an event handler
%%
%% @spec add_handler() -> ok | {'EXIT', Reason} | term()
%% @end
%%--------------------------------------------------------------------
add_handler() ->
    gen_event:add_handler(?SERVER, ?MODULE, []).

%%%===================================================================
%%% gen_event callbacks
%%%===================================================================

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Whenever a new event handler is added to an event manager,
%% this function is called to initialize the event handler.
%%
%% @spec init(Args) -> {ok, State}
%% @end
%%--------------------------------------------------------------------
init([]) ->
    {ok, #state{}}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Whenever an event manager receives an event sent using
%% gen_event:notify/2 or gen_event:sync_notify/2, this function is
%% called for each installed event handler to handle the event.
%%
%% @spec handle_event(Event, State) ->
%%                          {ok, State} |
%%                          {swap_handler, Args1, State1, Mod2, Args2} |
%%                          remove_handler
%% @end
%%--------------------------------------------------------------------

handle_event({info_report, _Gleader, {_Pid, _Type,
                                      {net_kernel, {'EXIT', _, Reason}}}},
             #state{report = Report} = State) ->
    NewReport = case format(Reason) of
                    [] -> Report;
                    Formatted -> [Formatted | Report]
                end,
    {ok, State#state{report = NewReport}};
handle_event(_Event, State) ->
    {ok, State}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Whenever an event manager receives a request sent using
%% gen_event:call/3,4, this function is called for the specified
%% event handler to handle the request.
%%
%% @spec handle_call(Request, State) ->
%%                   {ok, Reply, State} |
%%                   {swap_handler, Reply, Args1, State1, Mod2, Args2} |
%%                   {remove_handler, Reply}
%% @end
%%--------------------------------------------------------------------
handle_call(get_connection_report, State) ->
    {ok, lists:reverse(State#state.report), State#state{report = []}};
handle_call(_Request, State) ->
    Reply = ok,
    {ok, Reply, State}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% This function is called for each installed event handler when
%% an event manager receives any other message than an event or a
%% synchronous request (or a system message).
%%
%% @spec handle_info(Info, State) ->
%%                         {ok, State} |
%%                         {swap_handler, Args1, State1, Mod2, Args2} |
%%                         remove_handler
%% @end
%%--------------------------------------------------------------------
handle_info(_Info, State) ->
    {ok, State}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Whenever an event handler is deleted from an event manager, this
%% function is called. It should be the opposite of Module:init/1 and
%% do any necessary cleaning up.
%%
%% @spec terminate(Reason, State) -> void()
%% @end
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
    ok.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Convert process state when code is changed
%%
%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
%% @end
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%%%===================================================================
%%% Internal functions
%%%===================================================================
format({check_dflag_xnc_failed, _What}) ->
    {"  * Remote node uses an incompatible Erlang version ~n", []};
format({recv_challenge_failed, no_node, Node}) ->
    {"  * Node name (or hostname) mismatch: node ~tp believes its node name is not ~tp but something else.~n"
     "    All nodes and CLI tools must refer to node ~tp using the same name the node itself uses (see its logs to find out what it is)~n",
     [Node, Node, Node]};
format({recv_challenge_failed, Error}) ->
    {"  * Distribution failed unexpectedly while waiting for challenge: ~tp~n", [Error]};
format({recv_challenge_ack_failed, bad_cookie}) ->
    {"  * Authentication failed (rejected by the local node), please check the Erlang cookie~n", []};
format({recv_challenge_ack_failed, {error, closed}}) ->
    {"  * Authentication failed (rejected by the remote node), please check the Erlang cookie~n", []};
format({recv_status_failed, not_allowed}) ->
    {"  * This node is not on the list of nodes authorised by remote node (see net_kernel:allow/1)~n", []};
format({recv_status_failed, {error, closed}}) ->
    {"  * Remote host closed TCP connection before completing authentication. Is the Erlang distribution using TLS?~n", []};
format(setup_timer_timeout) ->
    {"  * TCP connection to remote host has timed out. Is the Erlang distribution using TLS?~n", []};
format(_) ->
    [].
