%%%-------------------------------------------------------------------
%%% @doc
%%% Macula Gateway Diagnostics Service
%%%
%%% Provides simple diagnostic procedures that clients can call to verify
%%% connectivity and test the gateway's RPC functionality.
%%%
%%% Available procedures:
%%%   - com.macula.diagnostics.hello - Returns a friendly greeting with gateway info
%%%   - com.macula.diagnostics.echo   - Echoes back the arguments sent by client
%%%   - com.macula.diagnostics.info   - Returns detailed gateway information
%%%
%%% Usage:
%%%   The diagnostics service automatically registers when the gateway starts.
%%%   Clients can call these procedures using the Macula SDK:
%%%
%%%   Elixir:
%%%     {:ok, result} = MaculaSdk.Client.call(client, "com.macula.diagnostics.hello", %{})
%%%
%%%   Result:
%%%     %{
%%%       "message" => "Hello from Macula Gateway!",
%%%       "gateway" => "macula@127.0.0.1",
%%%       "realm" => "com.example.realm",
%%%       "uptime_seconds" => 42,
%%%       "timestamp" => 1699612800
%%%     }
%%% @end
%%%-------------------------------------------------------------------
-module(macula_gateway_diagnostics).

-behaviour(gen_server).

%% API
-export([
    start_link/1,
    register_procedures/1
]).

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

-record(state, {
    gateway_pid :: pid(),
    realm :: binary(),
    started_at :: integer()
}).

%%%===================================================================
%%% API Functions
%%%===================================================================

%% @doc Start the diagnostics service
-spec start_link(proplists:proplist()) -> {ok, pid()} | {error, term()}.
start_link(Opts) ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, Opts, []).

%% @doc Register diagnostic procedures with the gateway
-spec register_procedures(pid()) -> ok.
register_procedures(GatewayPid) ->
    gen_server:cast(?MODULE, {register_procedures, GatewayPid}).

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

init(Opts) ->
    Realm = proplists:get_value(realm, Opts, <<"macula.default">>),

    io:format("Starting diagnostics service for realm: ~s~n", [Realm]),

    State = #state{
        gateway_pid = undefined,
        realm = Realm,
        started_at = erlang:system_time(second)
    },

    {ok, State}.

handle_call(_Request, _From, State) ->
    {reply, {error, unknown_request}, State}.

handle_cast({register_procedures, GatewayPid}, State) ->
    %% Register diagnostic procedures
    Procedures = [
        <<"com.macula.diagnostics.hello">>,
        <<"com.macula.diagnostics.echo">>,
        <<"com.macula.diagnostics.info">>
    ],

    lists:foreach(fun(Procedure) ->
        GatewayPid ! {register, self(), Procedure}
    end, Procedures),

    io:format("Registered ~p diagnostic procedures~n", [length(Procedures)]),

    {noreply, State#state{gateway_pid = GatewayPid}};

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

%% Handle RPC invocations from gateway
handle_info({invoke, CallerPid, CallId, Procedure, Args}, State) ->
    Result = handle_procedure(Procedure, Args, State),

    %% Send result back to caller
    CallerPid ! {call_result, CallId, Result},

    {noreply, State};

%% Handle registration confirmations
handle_info({registered, Procedure}, State) ->
    io:format("Diagnostic procedure registered: ~s~n", [Procedure]),
    {noreply, State};

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

terminate(_Reason, _State) ->
    ok.

%%%===================================================================
%%% Internal Functions
%%%===================================================================

%% @private
handle_procedure(<<"com.macula.diagnostics.hello">>, _Args, State) ->
    Uptime = erlang:system_time(second) - State#state.started_at,

    #{
        <<"message">> => <<"Hello from Macula Gateway!">>,
        <<"gateway">> => list_to_binary(atom_to_list(node())),
        <<"realm">> => State#state.realm,
        <<"uptime_seconds">> => Uptime,
        <<"timestamp">> => erlang:system_time(second),
        <<"version">> => <<"0.1.0">>,
        <<"protocol">> => <<"HTTP/3 (QUIC)">>
    };

handle_procedure(<<"com.macula.diagnostics.echo">>, Args, _State) ->
    #{
        <<"echo">> => Args,
        <<"timestamp">> => erlang:system_time(second)
    };

handle_procedure(<<"com.macula.diagnostics.info">>, _Args, State) ->
    Uptime = erlang:system_time(second) - State#state.started_at,

    %% Get memory info
    MemoryInfo = erlang:memory(),
    TotalMemory = proplists:get_value(total, MemoryInfo, 0),
    ProcessMemory = proplists:get_value(processes, MemoryInfo, 0),

    %% Get process count
    ProcessCount = erlang:system_info(process_count),

    #{
        <<"gateway">> => #{
            <<"node">> => list_to_binary(atom_to_list(node())),
            <<"realm">> => State#state.realm,
            <<"uptime_seconds">> => Uptime,
            <<"version">> => <<"0.1.0">>
        },
        <<"system">> => #{
            <<"otp_version">> => list_to_binary(erlang:system_info(otp_release)),
            <<"erts_version">> => list_to_binary(erlang:system_info(version)),
            <<"process_count">> => ProcessCount,
            <<"memory_bytes">> => TotalMemory,
            <<"process_memory_bytes">> => ProcessMemory
        },
        <<"timestamp">> => erlang:system_time(second)
    };

handle_procedure(_Unknown, _Args, _State) ->
    #{
        <<"error">> => <<"unknown_procedure">>,
        <<"timestamp">> => erlang:system_time(second)
    }.
