%%%-------------------------------------------------------------------
%%% @author anlicheng
%%% @copyright (C) 2025, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 08. 5月 2025 13:00
%%%-------------------------------------------------------------------
-module(event_stream_handler).
-author("anlicheng").

%% API
-export([init/2]).

init(Req0, Opts) ->
    ok = iot_log:set_metadata(),
    Method = binary_to_list(cowboy_req:method(Req0)),
    Path = binary_to_list(cowboy_req:path(Req0)),
    GetParams0 = cowboy_req:parse_qs(Req0),
    GetParams = maps:from_list(GetParams0),

    logger:debug("method: ~p, path: ~p,  get: ~p", [Method, Path, GetParams]),
    case parse_stream_params(GetParams) of
        {ok, UUID, TaskId} ->
            Req1 = cowboy_req:stream_reply(200, #{
                <<"Content-Type">> => <<"text/event-stream">>,
                <<"Cache-Control">> => <<"no-cache">>,
                <<"Connection">> => <<"keep-alive">>
            }, Req0),

            ok = iot_container_task:subscribe(UUID, TaskId, self()),
            receiver_events(TaskId, Req1),

            {ok, Req1, Opts};
        {error, Reason} ->
            Req1 = cowboy_req:reply(400, #{
                <<"Content-Type">> => <<"application/json">>
            }, iot_util:json_error(400, Reason), Req0),

            {ok, Req1, Opts}
    end.

receiver_events(TaskId, Req) ->
    receive
        {stream_data, TaskId, Type, Stream} ->
            Data = iolist_to_binary(json:encode(#{<<"type">> => Type, <<"stream">> => Stream})),
            Body = iolist_to_binary([<<"event: message\n">>, <<"data: ", Data/binary, "\n">>, <<"\n">>]),
            ok = cowboy_req:stream_body(Body, nofin, Req),

            receiver_events(TaskId, Req);
        {stream_close, TaskId, Reason} ->
            CloseFrame = iolist_to_binary([<<"event: close\n">>, <<"data: ", Reason/binary, "\n">>, <<"\n">>]),
            ok = cowboy_req:stream_body(CloseFrame, fin, Req)
    after 30000 ->
            ok = cowboy_req:stream_body(<<": heartbeat\n\n">>, nofin, Req),
            receiver_events(TaskId, Req)
    end.

-spec parse_stream_params(map()) -> {ok, binary(), integer()} | {error, binary()}.
parse_stream_params(#{<<"uuid">> := UUID, <<"task_id">> := TaskId0}) when is_binary(UUID), is_binary(TaskId0) ->
    try binary_to_integer(TaskId0) of
        TaskId when TaskId >= 0 ->
            {ok, UUID, TaskId};
        _ ->
            {error, <<"task_id must be non-negative">>}
    catch
        error:badarg ->
            {error, <<"task_id must be integer">>}
    end;
parse_stream_params(#{<<"task_id">> := _TaskId0}) ->
    {error, <<"uuid required">>};
parse_stream_params(_) ->
    {error, <<"uuid and task_id required">>}.
