-module(async_handler).
-behavior(cowboy_rest).

-export([set_result/1]).

-export([init/2]).
-export([allowed_methods/2]).
-export([content_types_accepted/2]).
-export([content_types_provided/2]).
-export([resource_exists/2]).
-export([from_json/2]).
-export([to_json/2]).


set_result({ReqID, {{_, 200, _}, _Headers, Result}}) ->
  set_task_result(ReqID, Result);

set_result({ReqID, {{_, 204, _}, _Headers, _Result}}) ->
  set_task_result(ReqID, succeeded);

set_result({ReqID, {{_, StatusCode, Status}, _Headers, _Result}}) ->
  set_task_result(ReqID, #{StatusCode => list_to_binary(Status)});

set_result({ReqID, {error, Reason}}) ->
  set_task_result(ReqID, #{error => Reason}).


init(Req, State) ->
  {cowboy_rest, Req, State}.

allowed_methods(Req, State) ->
  {[
    <<"GET">>,
    <<"HEAD">>,
    <<"POST">>,
    <<"OPTIONS">>
  ], Req, State}.

content_types_accepted(Req, State) ->
  {[
    {<<"application/json">>, from_json}
  ],
  Req, State}.

content_types_provided(Req, State) ->
  {[
    {<<"application/json">>, to_json}
  ], Req, State}.

resource_exists(Req, State) ->
  case cowboy_req:binding(task_id, Req) of
    undefined ->
      %% Collections always exist.
      {true, Req, State};
    TaskID ->
      case get_task(TaskID) of
        not_found ->
          {false, Req, State};
        Task ->
          {true, Req, State#{task_id => TaskID, task => Task}}
      end
  end.

from_json(Req, State) ->
  {ok, Body, Req1} = cowboy_req:read_body(Req),
  {ok, Task} = thoas:decode(Body),
  Result = set_task(Task),
  Req2 = cowboy_req:set_resp_body(thoas:encode(Result), Req1),
  {true, Req2, State}.

to_json(Req, #{task := Task} = State) ->
  {thoas:encode(Task), Req, State};

to_json(Req, State) ->
  Collection = get_collection(),
  {thoas:encode(Collection), Req, State}.

%%
%% Internal.
%%
table() -> workloads_async.

set_task(Task) ->
  {ok, ReqID} = forward(Task),
  TaskID = req_id_to_task_id(ReqID),
  Row = {TaskID, Task, in_progress},
  ets:insert(table(), Row),
  row_to_task(Row).

set_task_result(ReqID, Result) ->
  TaskID = req_id_to_task_id(ReqID),
  ets:update_element(table(), TaskID, {3, Result}).

get_task(TaskID) ->
  [Row] = ets:lookup(table(), TaskID),
  row_to_task(Row).

get_collection() ->
  [ row_to_task(Row) || Row <- ets:tab2list(table()) ].

forward(#{<<"uri">> := URI, <<"payload">> := Payload}) ->
  JSONBody = thoas:encode(Payload),
  httpc:request(
    post,
    {URI, [], "application/json", JSONBody},
    [],
    [{sync, false},
     {receiver, {?MODULE, set_result, []}}
    ]
  ).
req_id_to_task_id(ReqID) ->
  TaskID = base64:encode_to_string(
    term_to_binary(ReqID),
    #{mode => urlsafe}
  ),
  list_to_binary(TaskID).


row_to_task({TaskID, Task, Result}) ->
  #{id => TaskID,
    task => Task,
    result => Result
  }.