%%%-------------------------------------------------------------------
%%% @author Codex
%%% @doc
%%% 本地模拟 control api，用于替代 iot_api_client 依赖的远端接口。
%%% @end
%%%-------------------------------------------------------------------
-module(simulator_api_handler).

-include_lib("endpoint/include/endpoint.hrl").

-export([handle_request/4]).

-define(HOST_UUID, <<"qbxmjyzrkpntfgswaevodhluicqzxplkm">>).
-define(HOST_ID, 1).
-define(DEVICE_UUID, <<"sim-device-001">>).
-define(DEVICE_ID, 1).
-define(ENDPOINT_ID, 1).
-define(TABLE, ?MODULE).

-spec handle_request(string(), string(), map(), map()) ->
    {ok, non_neg_integer(), iodata()}.
handle_request("GET", "/simulator/get_all_hosts", _, _) ->
    {ok, 200, iot_util:json_data([?HOST_UUID])};
handle_request("GET", "/simulator/get_host_by_uuid", #{<<"uuid">> := UUID}, _) when is_binary(UUID) ->
    reply_host_by_uuid(UUID);
handle_request("GET", "/simulator/get_host_by_id", #{<<"host_id">> := HostIdBin}, _) when is_binary(HostIdBin) ->
    case binary_to_integer(HostIdBin) of
        ?HOST_ID ->
            {ok, 200, iot_util:json_data(host_info())};
        _ ->
            {ok, 200, iot_util:json_error(404, <<"host not found">>)}
    end;
handle_request("POST", "/simulator/change_host_status", _, #{<<"uuid">> := UUID, <<"new_status">> := Status})
        when is_binary(UUID), is_integer(Status) ->
    reply_change_host_status(UUID, Status);
handle_request("GET", "/simulator/get_host_devices", #{<<"host_id">> := HostIdBin}, _) when is_binary(HostIdBin) ->
    case binary_to_integer(HostIdBin) of
        ?HOST_ID ->
            {ok, 200, iot_util:json_data([device_info()])};
        _ ->
            {ok, 200, iot_util:json_error(404, <<"host not found">>)}
    end;
handle_request("GET", "/simulator/get_device_by_uuid", #{<<"device_uuid">> := DeviceUUID}, _) when is_binary(DeviceUUID) ->
    case DeviceUUID of
        ?DEVICE_UUID ->
            {ok, 200, iot_util:json_data(device_info())};
        _ ->
            {ok, 200, iot_util:json_error(404, <<"device not found">>)}
    end;
handle_request("POST", "/simulator/change_device_status", _, #{<<"device_uuid">> := DeviceUUID, <<"new_status">> := Status})
        when is_binary(DeviceUUID), is_integer(Status) ->
    reply_change_device_status(DeviceUUID, Status);
handle_request("GET", "/simulator/get_all_endpoints", _, _) ->
    {ok, 200, iot_util:json_data([endpoint_info()])};
handle_request("GET", "/simulator/get_endpoint", #{<<"id">> := IdBin}, _) when is_binary(IdBin) ->
    case binary_to_integer(IdBin) of
        ?ENDPOINT_ID ->
            {ok, 200, iot_util:json_data(endpoint_info())};
        _ ->
            {ok, 200, iot_util:json_error(404, <<"endpoint not found">>)}
    end;
handle_request("POST", "/simulator/endpoint_sink", _, Body) ->
    logger:debug("[simulator_api_handler] receive endpoint sink body: ~p", [Body]),
    {ok, 200, iot_util:json_data(<<"ok">>)};
handle_request(_, Path, _, _) ->
    PathBin = list_to_binary(Path),
    {ok, 200, iot_util:json_error(-1, <<"url: ", PathBin/binary, " not found">>)}.

%%%===================================================================
%%% Internal functions
%%%===================================================================

-spec reply_host_by_uuid(binary()) -> {ok, non_neg_integer(), iodata()}.
reply_host_by_uuid(?HOST_UUID) ->
    {ok, 200, iot_util:json_data(host_info())};
reply_host_by_uuid(_) ->
    {ok, 200, iot_util:json_error(404, <<"host not found">>)}.

-spec reply_change_host_status(binary(), integer()) -> {ok, non_neg_integer(), iodata()}.
reply_change_host_status(?HOST_UUID, Status) ->
    ok = put_state({host_status, ?HOST_UUID}, Status),
    {ok, 200, iot_util:json_data(1)};
reply_change_host_status(_, _) ->
    {ok, 200, iot_util:json_error(404, <<"host not found">>)}.

-spec reply_change_device_status(binary(), integer()) -> {ok, non_neg_integer(), iodata()}.
reply_change_device_status(?DEVICE_UUID, Status) ->
    ok = put_state({device_status, ?DEVICE_UUID}, Status),
    {ok, 200, iot_util:json_data(1)};
reply_change_device_status(_, _) ->
    {ok, 200, iot_util:json_error(404, <<"device not found">>)}.

-spec host_info() -> map().
host_info() ->
    #{
        <<"id">> => ?HOST_ID,
        <<"uuid">> => ?HOST_UUID,
        <<"authorize_status">> => 1,
        <<"status">> => get_state({host_status, ?HOST_UUID}, 1)
    }.

-spec device_info() -> map().
device_info() ->
    #{
        <<"id">> => ?DEVICE_ID,
        <<"host_id">> => ?HOST_ID,
        <<"device_uuid">> => ?DEVICE_UUID,
        <<"status">> => get_state({device_status, ?DEVICE_UUID}, 1)
    }.

-spec endpoint_info() -> map().
endpoint_info() ->
    #{
        <<"id">> => ?ENDPOINT_ID,
        <<"matcher">> => <<"simulator.metric">>,
        <<"title">> => <<"Simulator HTTP Endpoint">>,
        <<"type">> => <<"http">>,
        <<"config">> => #{
            <<"url">> => <<"http://127.0.0.1:18090/simulator/endpoint_sink">>,
            <<"pool_size">> => 4
        },
        <<"status">> => 1,
        <<"updated_at">> => iot_util:timestamp(),
        <<"created_at">> => iot_util:timestamp()
    }.

-spec ensure_table() -> ets:tid().
ensure_table() ->
    case ets:info(?TABLE) of
        undefined ->
            try ets:new(?TABLE, [named_table, public, set]) of
                Tid ->
                    Tid
            catch
                error:badarg ->
                    ?TABLE
            end;
        _ ->
            ?TABLE
    end.

-spec get_state(term(), term()) -> term().
get_state(Key, Default) ->
    _ = ensure_table(),
    case ets:lookup(?TABLE, Key) of
        [{_, Value}] ->
            Value;
        [] ->
            Default
    end.

-spec put_state(term(), term()) -> ok.
put_state(Key, Value) ->
    _ = ensure_table(),
    true = ets:insert(?TABLE, {Key, Value}),
    ok.
