% Licensed under the Apache License, Version 2.0 (the "License"); you may not
% use this file except in compliance with the License. You may obtain a copy of
% the License at
%
%   http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
% License for the specific language governing permissions and limitations under
% the License.

-module(fabric_rpc_tests).

-include_lib("couch/include/couch_eunit.hrl").
-include_lib("couch/include/couch_db.hrl").

main_test_() ->
    {
        setup,
        spawn,
        fun setup_all/0,
        fun teardown_all/1,
        [
            {
                foreach,
                fun setup_no_db_or_config/0,
                fun teardown_db/1,
                [
                    ?TDEF_FE(t_no_config_non_shard_db_create_succeeds)
                ]
            },
            {
                foreach,
                fun setup_shard/0,
                fun teardown_noop/1,
                [
                    ?TDEF_FE(t_no_db),
                    ?TDEF_FE(t_no_config_db_create_fails_for_shard),
                    ?TDEF_FE(t_no_config_db_create_fails_for_shard_rpc)
                ]
            },
            {
                foreach,
                fun setup_shard/0,
                fun teardown_db/1,
                [
                    ?TDEF_FE(t_db_create_with_config)
                ]
            }
        ]
    }.

setup_all() ->
    Ctx = test_util:start_couch([rexi, mem3, fabric]),
    DatabaseDir = config:get("couchdb", "database_dir"),
    Suffix = ?b2l(couch_uuids:random()),
    test_util:with_couch_server_restart(fun() ->
        config:set("couchdb", "database_dir", DatabaseDir ++ "/" ++ Suffix, _Persist = false)
    end),
    Ctx.

teardown_all(Ctx) ->
    config:delete("couchdb", "database_dir", false),
    test_util:stop_couch(Ctx).

setup_no_db_or_config() ->
    ?tempdb().

setup_shard() ->
    ?tempshard().

teardown_noop(_DbName) ->
    ok.

teardown_db(DbName) ->
    ok = couch_server:delete(DbName, []).

t_no_db(DbName) ->
    ?assertEqual({not_found, no_db_file}, couch_db:open_int(DbName, [?ADMIN_CTX])).

t_no_config_non_shard_db_create_succeeds(DbName) ->
    ?assertEqual({not_found, no_db_file}, couch_db:open_int(DbName, [?ADMIN_CTX])),
    ?assertEqual(DbName, mem3:dbname(DbName)),
    ?assertMatch({ok, _}, mem3_util:get_or_create_db(DbName, [?ADMIN_CTX])).

t_no_config_db_create_fails_for_shard(DbName) ->
    ?assertEqual({not_found, no_db_file}, couch_db:open_int(DbName, [?ADMIN_CTX])),
    ?assertException(
        throw, {error, missing_target}, mem3_util:get_or_create_db(DbName, [?ADMIN_CTX])
    ).

t_no_config_db_create_fails_for_shard_rpc(DbName) ->
    ?assertEqual({not_found, no_db_file}, couch_db:open_int(DbName, [?ADMIN_CTX])),
    ?assertException(
        throw, {error, missing_target}, mem3_util:get_or_create_db(DbName, [?ADMIN_CTX])
    ),
    MFA = {fabric_rpc, get_db_info, [DbName]},
    Ref = rexi:cast(node(), self(), MFA),
    Resp =
        receive
            Resp0 -> Resp0
        end,
    ?assertMatch({Ref, {'rexi_EXIT', {database_does_not_exist, _}}}, Resp).

t_db_create_with_config(DbName) ->
    MDbName = mem3:dbname(DbName),
    DbDoc = #doc{id = MDbName, body = test_db_doc()},

    ?assertEqual({not_found, no_db_file}, couch_db:open_int(DbName, [?ADMIN_CTX])),

    %% Write the dbs db config
    couch_util:with_db(mem3_sync:shards_db(), fun(Db) ->
        ?assertEqual({not_found, missing}, couch_db:open_doc(Db, MDbName, [ejson_body])),
        ?assertMatch({ok, _}, couch_db:update_docs(Db, [DbDoc]))
    end),

    %% Test get_or_create_db loads the properties as expected
    couch_util:with_db(mem3_sync:shards_db(), fun(Db) ->
        ?assertMatch({ok, _}, couch_db:open_doc(Db, MDbName, [ejson_body])),
        ?assertEqual({not_found, no_db_file}, couch_db:open_int(DbName, [?ADMIN_CTX])),
        Resp = mem3_util:get_or_create_db(DbName, [?ADMIN_CTX]),
        ?assertMatch({ok, _}, Resp),
        {ok, LDb} = Resp,

        {Body} = test_db_doc(),
        DbProps = mem3_util:get_shard_opts(Body),
        {Props} =
            case couch_db_engine:get_props(LDb) of
                undefined -> {[]};
                Else -> {Else}
            end,
        %% We don't normally store the default engine name
        EngineProps =
            case couch_db_engine:get_engine(LDb) of
                couch_bt_engine ->
                    [];
                EngineName ->
                    [{engine, EngineName}]
            end,
        ?assertEqual([{props, Props} | EngineProps], DbProps)
    end),
    ok = fabric:delete_db(MDbName).

test_db_doc() ->
    {[
        {<<"shard_suffix">>, ".1584997648"},
        {<<"changelog">>, [
            [<<"add">>, <<"00000000-7fffffff">>, <<"node1@127.0.0.1">>],
            [<<"add">>, <<"00000000-7fffffff">>, <<"node2@127.0.0.1">>],
            [<<"add">>, <<"00000000-7fffffff">>, <<"node3@127.0.0.1">>],
            [<<"add">>, <<"80000000-ffffffff">>, <<"node1@127.0.0.1">>],
            [<<"add">>, <<"80000000-ffffffff">>, <<"node2@127.0.0.1">>],
            [<<"add">>, <<"80000000-ffffffff">>, <<"node3@127.0.0.1">>]
        ]},
        {<<"by_node">>,
            {[
                {<<"node1@127.0.0.1">>, [<<"00000000-7fffffff">>, <<"80000000-ffffffff">>]},
                {<<"node2@127.0.0.1">>, [<<"00000000-7fffffff">>, <<"80000000-ffffffff">>]},
                {<<"node3@127.0.0.1">>, [<<"00000000-7fffffff">>, <<"80000000-ffffffff">>]}
            ]}},
        {<<"by_range">>,
            {[
                {<<"00000000-7fffffff">>, [
                    <<"node1@127.0.0.1">>, <<"node2@127.0.0.1">>, <<"node3@127.0.0.1">>
                ]},
                {<<"80000000-ffffffff">>, [
                    <<"node1@127.0.0.1">>, <<"node2@127.0.0.1">>, <<"node3@127.0.0.1">>
                ]}
            ]}},
        {<<"props">>,
            {[
                {partitioned, true},
                {hash, [couch_partition, hash, []]}
            ]}}
    ]}.
