#include "rtyserver_diag.h"

#include <library/cpp/testing/unittest/registar.h>

Y_UNIT_TEST_SUITE(TestRTYServerDiagState) {
    // Helper class
    struct TState final {
        TRTYServerDiagState Obj;
        bool IsSearching = false;

    public:
        void Reset() {
            *this = TState();
        }
        TRTYRecoveryStage GetRecoveryStage(bool isRepairing = false) {
            return Obj.GetRecoveryStage(isRepairing, IsSearching);
        }

        void SimEnableSearchCommand() {
            IsSearching = true;
            Obj.OnEnableSearchCommand();
        }

        void SimDisableSearchCommand() {
            IsSearching = false;
        }
    };

    //
    // No docfetcher. None or Repair should be diagnosted
    //
    Y_UNIT_TEST(WithoutDocfetcher) {

        //Lifecycle 1 : no repair
        TState State;
        State.Reset();
        UNIT_ASSERT(State.GetRecoveryStage() == TRTYRecoveryStage::None);
        State.SimEnableSearchCommand();
        UNIT_ASSERT(State.GetRecoveryStage() == TRTYRecoveryStage::None);
        State.SimDisableSearchCommand();
        UNIT_ASSERT(State.GetRecoveryStage() == TRTYRecoveryStage::None);
        State.SimEnableSearchCommand();
        UNIT_ASSERT(State.GetRecoveryStage() == TRTYRecoveryStage::None);

        //Lifecycle 2 : repair on start
        State.Reset();
        UNIT_ASSERT(State.GetRecoveryStage() == TRTYRecoveryStage::None);
        UNIT_ASSERT(State.GetRecoveryStage(true) == TRTYRecoveryStage::Repair);
        UNIT_ASSERT(State.GetRecoveryStage() == TRTYRecoveryStage::None);
        State.SimDisableSearchCommand();
        UNIT_ASSERT(State.GetRecoveryStage() == TRTYRecoveryStage::None);
        UNIT_ASSERT(State.GetRecoveryStage(true) == TRTYRecoveryStage::Repair);
        UNIT_ASSERT(State.GetRecoveryStage() == TRTYRecoveryStage::None);
    }

    //An invariant
    void ExpectNone_BecauseSearchWasActive(const TRTYServerDiagState &obj) {
        UNIT_ASSERT_C(obj.GetWasSearching(), "bad TState");

        //as the searcher was active in the past, it should not matter whether we are banned now - return none
        UNIT_ASSERT(obj.GetRecoveryStage(false, false) == TRTYRecoveryStage::None);
        UNIT_ASSERT(obj.GetRecoveryStage(false, true) == TRTYRecoveryStage::None);

        //For the sake of completeness only
        UNIT_ASSERT(obj.GetRecoveryStage(true, false) == TRTYRecoveryStage::None);
        UNIT_ASSERT(obj.GetRecoveryStage(true, true) == TRTYRecoveryStage::None);
    }

    Y_UNIT_TEST(StageNone) {
        //
        // Cases in which nothing should be diagnosed
        //

        TState State;
        //Lifecycle 1 : normal start with docfetcher enabled
        State.Reset();
        UNIT_ASSERT(State.Obj.GetRecoveryStage(false, false) == TRTYRecoveryStage::None);
        UNIT_ASSERT(State.Obj.GetRecoveryStage(false, true) == TRTYRecoveryStage::None);
        State.SimEnableSearchCommand();
        UNIT_ASSERT(State.GetRecoveryStage() == TRTYRecoveryStage::None);
        State.SimDisableSearchCommand();
        UNIT_ASSERT(State.GetRecoveryStage() == TRTYRecoveryStage::None);
        State.SimEnableSearchCommand();
        UNIT_ASSERT(State.GetRecoveryStage() == TRTYRecoveryStage::None);
        ExpectNone_BecauseSearchWasActive(State.Obj);

        //Lifecycle 2 : starts as banned
        State.Reset();
        State.SimDisableSearchCommand(); //now it is banned
        UNIT_ASSERT(State.GetRecoveryStage() == TRTYRecoveryStage::None);
        //docfetcher starts, fetches...
        State.SimEnableSearchCommand();
        UNIT_ASSERT(State.GetRecoveryStage() == TRTYRecoveryStage::None);
        ExpectNone_BecauseSearchWasActive(State.Obj);
    }

    Y_UNIT_TEST(StageRepair) {
        TState State;

        State.Reset();
        UNIT_ASSERT(State.GetRecoveryStage() == TRTYRecoveryStage::None);
        //repair
        UNIT_ASSERT(State.GetRecoveryStage(true) == TRTYRecoveryStage::Repair);
        //repair completed
        UNIT_ASSERT(State.GetRecoveryStage() == TRTYRecoveryStage::None);

        State.SimEnableSearchCommand();
        UNIT_ASSERT(State.GetRecoveryStage() == TRTYRecoveryStage::None);
        ExpectNone_BecauseSearchWasActive(State.Obj);
    }

    Y_UNIT_TEST(StageSync) {
        TState State;

        State.Reset();
        UNIT_ASSERT(State.GetRecoveryStage() == TRTYRecoveryStage::None);

        //the first stream synchronizes...
        State.Obj.OnSync();
        UNIT_ASSERT(State.GetRecoveryStage() == TRTYRecoveryStage::Sync);
        State.Obj.OnSyncCompleted();
        UNIT_ASSERT(State.GetRecoveryStage() == TRTYRecoveryStage::PostSync);

        //the second one synchronizes...
        State.Obj.OnSync();
        UNIT_ASSERT(State.GetRecoveryStage() == TRTYRecoveryStage::Sync);
        State.Obj.OnSyncCompleted();
        UNIT_ASSERT(State.GetRecoveryStage() == TRTYRecoveryStage::PostSync);
        //...now the synchronization is complete

        //Should not happen, but: if the search was somehow enabled without EnableSearch, show None
        UNIT_ASSERT(State.Obj.GetRecoveryStage(false, true) == TRTYRecoveryStage::None);
        UNIT_ASSERT(State.Obj.GetRecoveryStage(false, false) == TRTYRecoveryStage::PostSync); //yep

        //Normally we would expect that if Sync has happened, then there will be an explicit EnableSearch command after it.
        State.SimEnableSearchCommand();
        ExpectNone_BecauseSearchWasActive(State.Obj);
        State.SimDisableSearchCommand();
        ExpectNone_BecauseSearchWasActive(State.Obj);
    }

    Y_UNIT_TEST(RepairThenSync) {
        TState State;

        State.Reset();
        UNIT_ASSERT(State.GetRecoveryStage() == TRTYRecoveryStage::None);
        //repair
        UNIT_ASSERT(State.GetRecoveryStage(true) == TRTYRecoveryStage::Repair);
        //repair completed
        UNIT_ASSERT(State.GetRecoveryStage() == TRTYRecoveryStage::None);

        //synchronization starts
        State.Obj.OnSync();
        UNIT_ASSERT(State.GetRecoveryStage() == TRTYRecoveryStage::Sync);
        State.SimDisableSearchCommand();
        UNIT_ASSERT(State.GetRecoveryStage() == TRTYRecoveryStage::Sync);
        State.Obj.OnSyncCompleted();
        UNIT_ASSERT(State.GetRecoveryStage() == TRTYRecoveryStage::PostSync);

        //synchronization complete
        State.SimEnableSearchCommand();
        ExpectNone_BecauseSearchWasActive(State.Obj);
        State.SimDisableSearchCommand();
        ExpectNone_BecauseSearchWasActive(State.Obj);
    }
}

