%%%-------------------------------------------------------------------
%%% @author licheng5
%%% @copyright (C) 2020, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 26. 4月 2020 3:36 下午
%%%-------------------------------------------------------------------
-module(container_handler).
-author("licheng5").
-include("iot.hrl").

-define(REQ_TIMEOUT, 10000).

%% API
-export([handle_request/4]).

handle_request("GET", "/container/get_all", #{<<"uuid">> := UUID}, _) when is_binary(UUID) ->
    %% 检查ConfigJson是否是合法的json字符串
    case iot_host:get_pid(UUID) of
        undefined ->
            {ok, 200, iot_util:json_error(-1, <<"host not found">>)};
        Pid when is_pid(Pid) ->
                    case iot_host:get_containers(Pid) of
                {ok, Ref} ->
                    case iot_host:await_reply(Pid, Ref, ?REQ_TIMEOUT) of
                        ok ->
                            {ok, 200, request_success_response(<<"ok">>)};
                        {ok, Result} ->
                            {ok, 200, request_success_response(Result)};
                        {error, Reason} ->
                            request_error_http_response(Reason)
                    end;
                {error, Reason} when is_binary(Reason) ->
                    {ok, 200, iot_util:json_error(-1, Reason)}
            end
    end;

%% 下发config.json, 微服务接受后，保存服务配置
handle_request("POST", "/container/push_config", _,
    #{<<"uuid">> := UUID, <<"container_name">> := ContainerName, <<"config">> := Config, <<"timeout">> := Timeout0})
    when is_binary(UUID), is_binary(ContainerName), is_binary(Config), is_integer(Timeout0) ->

    %% 检查ConfigJson是否是合法的json字符串
    true = iot_util:is_json(Config),
    case iot_host:get_pid(UUID) of
        undefined ->
            {ok, 200, iot_util:json_error(-1, <<"host not found">>)};
        Pid when is_pid(Pid) ->
            Timeout = Timeout0 * 1000,
            case iot_host:config_container(Pid, ContainerName, Config) of
                {ok, Ref} ->
                    case iot_host:await_reply(Pid, Ref, Timeout) of
                        ok ->
                            {ok, 200, request_success_response(<<"ok">>)};
                        {ok, Result} ->
                            {ok, 200, request_success_response(Result)};
                        {error, Reason} ->
                            request_error_http_response(Reason)
                    end;
                {error, Reason} when is_binary(Reason) ->
                    {ok, 200, iot_util:json_error(-1, Reason)}
            end
    end;

%% 部署微服务
handle_request("POST", "/container/deploy", _, #{<<"uuid">> := UUID, <<"task_id">> := TaskId, <<"config">> := Config})
    when is_binary(UUID), is_integer(TaskId), is_map(Config) ->
    case iot_host:get_pid(UUID) of
        undefined ->
            {ok, 200, iot_util:json_error(404, <<"host not found">>)};
        Pid when is_pid(Pid) ->
            case iot_container_task_sup:ensure_started(UUID, TaskId) of
                {ok, _TaskPid} ->
                    handle_deploy_container(Pid, UUID, TaskId, Config);
                {error, Reason} ->
                    {ok, 500, iot_util:json_error(500, reason_to_binary(Reason))}
            end
    end;

%% 启动服务
handle_request("POST", "/container/start", _, #{<<"uuid">> := UUID, <<"container_name">> := ContainerName}) when is_binary(UUID), is_binary(ContainerName) ->
    case iot_host:get_pid(UUID) of
        undefined ->
            {ok, 200, iot_util:json_error(404, <<"host not found">>)};
        Pid when is_pid(Pid) ->
            case iot_host:start_container(Pid, ContainerName) of
                {ok, Ref} ->
                    case iot_host:await_reply(Pid, Ref, ?REQ_TIMEOUT) of
                        ok ->
                            {ok, 200, request_success_response(<<"ok">>)};
                        {ok, Result} ->
                            {ok, 200, request_success_response(Result)};
                        {error, Reason} ->
                            request_error_http_response(Reason)
                    end;
                {error, Reason} when is_binary(Reason) ->
                    {ok, 200, iot_util:json_error(400, Reason)}
            end
    end;

%% 停止服务
handle_request("POST", "/container/stop", _, #{<<"uuid">> := UUID, <<"container_name">> := ContainerName}) when is_binary(UUID), is_binary(ContainerName) ->
    case iot_host:get_pid(UUID) of
        undefined ->
            {ok, 200, iot_util:json_error(404, <<"host not found">>)};
        Pid when is_pid(Pid) ->
            case iot_host:stop_container(Pid, ContainerName) of
                {ok, Ref} ->
                    case iot_host:await_reply(Pid, Ref, ?REQ_TIMEOUT) of
                        ok ->
                            {ok, 200, request_success_response(<<"ok">>)};
                        {ok, Result} ->
                            {ok, 200, request_success_response(Result)};
                        {error, Reason} ->
                            request_error_http_response(Reason)
                    end;
                {error, Reason} when is_binary(Reason) ->
                    {ok, 200, iot_util:json_error(400, Reason)}
            end
    end;

handle_request("POST", "/container/kill", _, #{<<"uuid">> := UUID, <<"container_name">> := ContainerName}) when is_binary(UUID), is_binary(ContainerName) ->
    case iot_host:get_pid(UUID) of
        undefined ->
            {ok, 200, iot_util:json_error(404, <<"host not found">>)};
        Pid when is_pid(Pid) ->
            case iot_host:kill_container(Pid, ContainerName) of
                {ok, Ref} ->
                    case iot_host:await_reply(Pid, Ref, ?REQ_TIMEOUT) of
                        ok ->
                            {ok, 200, request_success_response(<<"ok">>)};
                        {ok, Result} ->
                            {ok, 200, request_success_response(Result)};
                        {error, Reason} ->
                            request_error_http_response(Reason)
                    end;
                {error, Reason} when is_binary(Reason) ->
                    {ok, 200, iot_util:json_error(400, Reason)}
            end
    end;

%% 删除容器
handle_request("POST", "/container/remove", _, #{<<"uuid">> := UUID, <<"container_name">> := ContainerName}) when is_binary(UUID), is_binary(ContainerName) ->
    case iot_host:get_pid(UUID) of
        undefined ->
            {ok, 200, iot_util:json_error(404, <<"host not found">>)};
        Pid when is_pid(Pid) ->
            case iot_host:remove_container(Pid, ContainerName) of
                {ok, Ref} ->
                    case iot_host:await_reply(Pid, Ref, ?REQ_TIMEOUT) of
                        ok ->
                            {ok, 200, request_success_response(<<"ok">>)};
                        {ok, Result} ->
                            {ok, 200, request_success_response(Result)};
                        {error, Reason} ->
                            request_error_http_response(Reason)
                    end;
                {error, Reason} when is_binary(Reason) ->
                    {ok, 200, iot_util:json_error(400, Reason)}
            end
    end;

handle_request(_, Path, _, _) ->
    Path1 = list_to_binary(Path),
    {ok, 200, iot_util:json_error(-1, <<"url: ", Path1/binary, " not found">>)}.

request_success_response(Result) when is_binary(Result) ->
    case decode_json_bytes(Result) of
        {ok, Data} ->
            iot_util:json_data(Data);
        error ->
            iot_util:json_data(Result)
    end;
request_success_response(Result) ->
    iot_util:json_data(Result).

request_error_response(Code, Reason) when is_integer(Code), is_binary(Reason) ->
    case decode_json_bytes(Reason) of
        {ok, #{<<"message">> := Message}} when is_binary(Message) ->
            iot_util:json_error(Code, Message);
        {ok, Message} when is_binary(Message) ->
            iot_util:json_error(Code, Message);
        _ ->
            iot_util:json_error(Code, Reason)
    end.

-spec request_error_http_response(Reason :: term()) ->
    {ok, HttpStatus :: 400 | 504, Body :: binary()}.
request_error_http_response(Reason) ->
    HttpStatus = request_error_status(Reason),
    {ok, HttpStatus, request_error_response(HttpStatus, reason_to_binary(Reason))}.

-spec request_error_status(Reason :: term()) -> integer().
request_error_status(timeout) ->
    504;
request_error_status(<<"timeout">>) ->
    504;
request_error_status(_) ->
    400.

-spec reason_to_binary(term()) -> binary().
reason_to_binary(Reason) when is_binary(Reason) ->
    Reason;
reason_to_binary(timeout) ->
    <<"timeout">>;
reason_to_binary(invalid_response) ->
    <<"invalid response">>;
reason_to_binary(Reason) ->
    unicode:characters_to_binary(io_lib:format("~p", [Reason])).

decode_json_bytes(Data) when is_binary(Data) ->
    case catch json:decode(Data) of
        {'EXIT', _} ->
            error;
        {error, _} ->
            error;
        Decoded ->
            {ok, Decoded}
    end.

-spec handle_deploy_container(pid(), binary(), integer(), map()) ->
    {ok, 200 | 400 | 504, binary()}.
handle_deploy_container(Pid, UUID, TaskId, Config) ->
    case iot_host:deploy_container(Pid, TaskId, Config) of
        {ok, Ref} ->
            case iot_host:await_reply(Pid, Ref, ?REQ_TIMEOUT) of
                ok ->
                    {ok, 200, request_success_response(<<"ok">>)};
                {ok, Result} ->
                    {ok, 200, request_success_response(Result)};
                {error, Reason} ->
                    ok = iot_container_task_sup:fail(UUID, TaskId, Reason),
                    request_error_http_response(Reason)
            end;
        {error, Reason} when is_binary(Reason) ->
            ok = iot_container_task_sup:fail(UUID, TaskId, Reason),
            {ok, 200, iot_util:json_error(400, Reason)}
    end.
