% 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(cpse_test_purge_replication).
-compile(export_all).
-compile(nowarn_export_all).

-include_lib("eunit/include/eunit.hrl").
-include_lib("couch/include/couch_db.hrl").
-include_lib("mem3/include/mem3.hrl").

setup_all() ->
    cpse_util:setup_all([mem3, fabric, couch_replicator]).

setup_each() ->
    {ok, Src} = cpse_util:create_db(),
    {ok, Tgt} = cpse_util:create_db(),
    {couch_db:name(Src), couch_db:name(Tgt)}.

teardown_each({SrcDb, TgtDb}) ->
    ok = couch_server:delete(SrcDb, []),
    ok = couch_server:delete(TgtDb, []).

cpse_purge_http_replication({Source, Target}) ->
    {ok, Rev1} = cpse_util:save_doc(Source, {[{'_id', foo}, {vsn, 1}]}),

    cpse_util:assert_db_props(?MODULE, ?LINE, Source, [
        {doc_count, 1},
        {del_doc_count, 0},
        {update_seq, 1},
        {changes, 1},
        {purge_seq, 0},
        {purge_infos, []}
    ]),

    RepObject =
        {[
            {<<"source">>, db_url(Source)},
            {<<"target">>, db_url(Target)}
        ]},

    {ok, _} = couch_replicator:replicate(RepObject, ?ADMIN_USER),
    {ok, Doc1} = cpse_util:open_doc(Target, foo),

    cpse_util:assert_db_props(?MODULE, ?LINE, Target, [
        {doc_count, 1},
        {del_doc_count, 0},
        {update_seq, 1},
        {changes, 1},
        {purge_seq, 0},
        {purge_infos, []}
    ]),

    PurgeInfos = [
        {cpse_util:uuid(), <<"foo">>, [Rev1]}
    ],

    {ok, [{ok, PRevs}]} = cpse_util:purge(Source, PurgeInfos),
    ?assertEqual([Rev1], PRevs),

    cpse_util:assert_db_props(?MODULE, ?LINE, Source, [
        {doc_count, 0},
        {del_doc_count, 0},
        {update_seq, 2},
        {changes, 0},
        {purge_seq, 1},
        {purge_infos, PurgeInfos}
    ]),

    % Show that a purge on the source is
    % not replicated to the target
    {ok, _} = couch_replicator:replicate(RepObject, ?ADMIN_USER),
    {ok, Doc2} = cpse_util:open_doc(Target, foo),
    [Rev2] = Doc2#doc_info.revs,
    ?assertEqual(Rev1, Rev2#rev_info.rev),
    ?assertEqual(Doc1, Doc2),

    cpse_util:assert_db_props(?MODULE, ?LINE, Target, [
        {doc_count, 1},
        {del_doc_count, 0},
        {update_seq, 1},
        {changes, 1},
        {purge_seq, 0},
        {purge_infos, []}
    ]),

    % Show that replicating from the target
    % back to the source reintroduces the doc
    RepObject2 =
        {[
            {<<"source">>, db_url(Target)},
            {<<"target">>, db_url(Source)}
        ]},

    {ok, _} = couch_replicator:replicate(RepObject2, ?ADMIN_USER),
    {ok, Doc3} = cpse_util:open_doc(Source, foo),
    [Revs3] = Doc3#doc_info.revs,
    ?assertEqual(Rev1, Revs3#rev_info.rev),

    cpse_util:assert_db_props(?MODULE, ?LINE, Source, [
        {doc_count, 1},
        {del_doc_count, 0},
        {update_seq, 3},
        {changes, 1},
        {purge_seq, 1},
        {purge_infos, PurgeInfos}
    ]).

cpse_purge_internal_repl_disabled({Source, Target}) ->
    cpse_util:with_config([{"mem3", "replicate_purges", "false"}], fun() ->
        repl(Source, Target),

        {ok, [Rev1, Rev2]} = cpse_util:save_docs(Source, [
            {[{'_id', foo1}, {vsn, 1}]},
            {[{'_id', foo2}, {vsn, 2}]}
        ]),

        repl(Source, Target),

        PurgeInfos1 = [
            {cpse_util:uuid(), <<"foo1">>, [Rev1]}
        ],
        {ok, [{ok, PRevs1}]} = cpse_util:purge(Source, PurgeInfos1),
        ?assertEqual([Rev1], PRevs1),

        PurgeInfos2 = [
            {cpse_util:uuid(), <<"foo2">>, [Rev2]}
        ],
        {ok, [{ok, PRevs2}]} = cpse_util:purge(Target, PurgeInfos2),
        ?assertEqual([Rev2], PRevs2),

        SrcShard = make_shard(Source),
        TgtShard = make_shard(Target),
        ?assertEqual({ok, 0}, mem3_rep:go(SrcShard, TgtShard)),
        ?assertEqual({ok, 0}, mem3_rep:go(TgtShard, SrcShard)),

        ?assertMatch({ok, #doc_info{}}, cpse_util:open_doc(Source, <<"foo2">>)),
        ?assertMatch({ok, #doc_info{}}, cpse_util:open_doc(Target, <<"foo1">>))
    end).

cpse_purge_repl_simple_pull({Source, Target}) ->
    repl(Source, Target),

    {ok, Rev} = cpse_util:save_doc(Source, {[{'_id', foo}, {vsn, 1}]}),
    repl(Source, Target),

    PurgeInfos = [
        {cpse_util:uuid(), <<"foo">>, [Rev]}
    ],
    {ok, [{ok, PRevs}]} = cpse_util:purge(Target, PurgeInfos),
    ?assertEqual([Rev], PRevs),
    repl(Source, Target).

cpse_purge_repl_simple_push({Source, Target}) ->
    repl(Source, Target),

    {ok, Rev} = cpse_util:save_doc(Source, {[{'_id', foo}, {vsn, 1}]}),
    repl(Source, Target),

    PurgeInfos = [
        {cpse_util:uuid(), <<"foo">>, [Rev]}
    ],
    {ok, [{ok, PRevs}]} = cpse_util:purge(Source, PurgeInfos),
    ?assertEqual([Rev], PRevs),
    repl(Source, Target).

repl(Source, Target) ->
    SrcShard = make_shard(Source),
    TgtShard = make_shard(Target),

    ?assertEqual({ok, 0}, mem3_rep:go(SrcShard, TgtShard)),

    SrcTerm = cpse_util:db_as_term(Source, replication),
    TgtTerm = cpse_util:db_as_term(Target, replication),

    Diff = cpse_util:term_diff(SrcTerm, TgtTerm),
    ?assertEqual(nodiff, Diff).

make_shard(DbName) ->
    #shard{
        name = DbName,
        node = node(),
        dbname = DbName,
        range = [0, 16#FFFFFFFF]
    }.

db_url(DbName) ->
    Addr = config:get("httpd", "bind_address", "127.0.0.1"),
    Port = mochiweb_socket_server:get(couch_httpd, port),
    Url = ?l2b(io_lib:format("http://~s:~b/~s", [Addr, Port, DbName])),
    test_util:wait(fun() ->
        case test_request:get(?b2l(Url)) of
            {ok, 200, _, _} -> ok;
            _ -> wait
        end
    end),
    Url.
