a cabal implementation in erlang
% SPDX-FileCopyrightText: 2023 Henry Bubert
%
% SPDX-License-Identifier: LGPL-2.1-or-later

-module(cabal_basic_SUITE).

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

post_request_test() ->
    [{name, _}, {binary, Bin}, {obj, TestObj}] = examples:post_request(),
    {ok, [Msg]} = cabal_wire:split_messages(Bin),
    {ok, Decoded} = cabal_wire:decode(Msg),
    [Header, [{ttl, 1}, {hashes, Hashes}]] = Decoded,
    assert_header(Header, TestObj),
    TestHashes = maps:get(<<"hashes">>, TestObj),
    ?assertEqual(2, proplists:get_value(msgType, Header)),
    ?assertEqual(length(TestHashes), length(Hashes)).
% TODO: assert hash values

cancel_request_test() ->
    [{name, _}, {binary, Bin}, {obj, TestObj}] = examples:cancel_request(),
    {ok, [Msg]} = cabal_wire:split_messages(Bin),
    {ok, Decoded} = cabal_wire:decode(Msg),
    [Header, [{ttl, 1}, {cancelId, CancelId}]] = Decoded,
    assert_header(Header, TestObj),
    ?assertEqual(3, proplists:get_value(msgType, Header)),
    CancelId = hex:hexstr_to_bin(binary_to_list(maps:get(<<"cancelid">>, TestObj))).

channel_time_range_request_test() ->
    [{name, _}, {binary, Bin}, {obj, TestObj}] = examples:channel_time_range_request(),
    {ok, [Msg]} = cabal_wire:split_messages(Bin),
    {ok, Decoded} = cabal_wire:decode(Msg),
    [
        Header,
        [
            {ttl, 1},
            {channel, Channel},
            {timestart, TimeStart},
            {timeend, TimeEnd},
            {limit, Limit}
        ]
    ] = Decoded,
    assert_header(Header, TestObj),
    ?assertEqual(4, proplists:get_value(msgType, Header)),
    WantCh = binary_to_list(maps:get(<<"channel">>, TestObj)),
    ?assertEqual(WantCh, Channel),
    ?assertEqual(maps:get(<<"timeStart">>, TestObj), TimeStart),
    ?assertEqual(maps:get(<<"timeEnd">>, TestObj), TimeEnd),
    ?assertEqual(maps:get(<<"limit">>, TestObj), Limit).

channel_state_request_test() ->
    [{name, _}, {binary, Bin}, {obj, TestObj}] = examples:channel_state_request(),
    {ok, [Msg]} = cabal_wire:split_messages(Bin),
    {ok, Decoded} = cabal_wire:decode(Msg),
    [
        Header,
        [
            {ttl, 1},
            {channel, Channel},
            {future, Future}
        ]
    ] = Decoded,
    assert_header(Header, TestObj),
    ?assertEqual(5, proplists:get_value(msgType, Header)),
    WantCh = binary_to_list(maps:get(<<"channel">>, TestObj)),
    ?assertEqual(WantCh, Channel),
    ?assertEqual(maps:get(<<"future">>, TestObj), Future).

channel_list_request_test() ->
    [{name, _}, {binary, Bin}, {obj, TestObj}] = examples:channel_list_request(),
    {ok, [Msg]} = cabal_wire:split_messages(Bin),
    {ok, Decoded} = cabal_wire:decode(Msg),
    [
        Header,
        [
            {ttl, 1},
            {offset, 0},
            {limit, 20}
        ]
    ] = Decoded,
    ?assertEqual(6, proplists:get_value(msgType, Header)),
    assert_header(Header, TestObj).

hash_response_test() ->
    [{name, _}, {binary, Bin}, {obj, TestObj}] = examples:hash_response(),
    {ok, [Msg]} = cabal_wire:split_messages(Bin),
    {ok, Decoded} = cabal_wire:decode(Msg),
    [Header, [{hashes, Hashes}]] = Decoded,
    ?assertEqual(0, proplists:get_value(msgType, Header)),
    % TODO: assert Hash values
    ?assertEqual(length(maps:get(<<"hashes">>, TestObj)), length(Hashes)),
    assert_header(Header, TestObj).

post_response_test() ->
    [{name, _}, {binary, Bin}, {obj, TestObj}] = examples:post_response(),
    {ok, [Msg]} = cabal_wire:split_messages(Bin),
    {ok, Decoded} = cabal_wire:decode(Msg),
    [Header, [{posts, Posts}]] = Decoded,
    ?assertEqual(1, proplists:get_value(msgType, Header)),
    ?assertEqual(1, length(Posts)),
    ?assert(is_binary(lists:nth(1, Posts))),
    assert_header(Header, TestObj).

channel_list_response_test() ->
    [{name, _}, {binary, Bin}, {obj, TestObj}] = examples:channel_list_response(),
    {ok, [Msg]} = cabal_wire:split_messages(Bin),
    {ok, Decoded} = cabal_wire:decode(Msg),
    [Header, [{channels, ["default", "dev", "introduction"]}]] = Decoded,
    ?assertEqual(7, proplists:get_value(msgType, Header)),
    assert_header(Header, TestObj).

%% full circle
encode_channel_state_req_test() ->
    TH = make_test_header(),
    Bin = cabal_wire:encode_channel_state_request(TH, "Foo", true),
    {ok, [Msg]} = cabal_wire:split_messages(Bin),
    {ok, [Header, Body]} = cabal_wire:decode(Msg),
    [{requestId, Req}, {circuitId, Circ}, _] = TH,
    ?assertEqual(Req, proplists:get_value(requestId, Header)),
    ?assertEqual(Circ, proplists:get_value(circuitId, Header)),
    ?assertEqual("Foo", proplists:get_value(channel, Body)),
    ?assertEqual(1, proplists:get_value(future, Body)).

% low-level helper

chunk_messages_test() ->
    A = crypto:strong_rand_bytes(5),
    ALen = cabal_wire:encode_varint(byte_size(A)),
    B = crypto:strong_rand_bytes(10),
    BLen = cabal_wire:encode_varint(byte_size(B)),
    C = crypto:strong_rand_bytes(5),
    CLen = cabal_wire:encode_varint(byte_size(C)),
    Payload = <<ALen/bytes, A/bytes, BLen/bytes, B/bytes, CLen/bytes, C/bytes>>,
    {ok, [A, B, C]} = cabal_wire:split_messages(Payload).

% Helpers
%%%%%%%%%

make_test_header() ->
    [
        {requestId, crypto:strong_rand_bytes(4)},
        {circuitId, crypto:strong_rand_bytes(4)},
        {ttl, rand:uniform(20)}
    ].

assert_header(Header, TestObj) ->
    ?assertEqual(maps:get(<<"msgType">>, TestObj), proplists:get_value(msgType, Header)),
    ReqId = hex:hexstr_to_bin(binary_to_list(maps:get(<<"reqid">>, TestObj))),
    ?assertEqual(ReqId, proplists:get_value(requestId, Header)).