#include <balancer/kernel/dns/its_switch.h>

#include <balancer/kernel/helpers/errors.h>

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


using namespace ::testing;

namespace NSrvKernel::NDns::NTests {

class TAsyncResolverMock : public IResolver {
public:
    MOCK_METHOD(TErrorOr<TSockAddrInfo>, DoResolve, (const TResolveInfo&, bool, TInstant), (override));
    MOCK_METHOD(void, DoResetCache, (), (override));
};

class TThreadedResolveMock : public IResolver {
public:
    MOCK_METHOD(TErrorOr<TSockAddrInfo>, DoResolve, (const TResolveInfo&, bool, TInstant), (override));
    MOCK_METHOD(void, DoResetCache, (), (override));
};

class TModeCheckerMock : public IModeChecker {
public:
    MOCK_METHOD(ECurrentMode, GetCurrentMode, (), (override));
};

class TItsSwitchFixture : public NUnitTest::TBaseFixture {
public:
    TItsSwitchFixture()
    {
        Config_.SwitchDnsFile = ItsFileName_;
        Config_.SwitchDnsCheckTimeout = TDuration::MilliSeconds(1);
        std::remove(ItsFileName_.c_str());

        Recreate();
    }

    virtual ~TItsSwitchFixture()
    {
        std::remove(ItsFileName_.c_str());
    }

    void Recreate()
    {
        auto asyncResolver = MakeHolder<TAsyncResolverMock>();
        AsyncMock_ = asyncResolver.Get();
        auto threadedResolver = MakeHolder<TThreadedResolveMock>();
        ThreadedMock_ = threadedResolver.Get();
        auto checker = MakeHolder<TModeCheckerMock>();
        Checker_ = checker.Get();

        Resolver_ = MakeHolder<TItsSwitch>(std::move(asyncResolver), std::move(threadedResolver),
                                           std::move(checker), Config_);
    }

protected:
    TAsyncResolverMock* AsyncMock_ = nullptr;
    TThreadedResolveMock* ThreadedMock_ = nullptr;
    TModeCheckerMock* Checker_ = nullptr;
    TString ItsFileName_ = "/tmp/async_dns_test";
    TResolverConfig Config_;
    TSharedFiles SharedFiles_;
    THolder<IResolver> Resolver_;

    TResolveInfo Host1Info_{"test.host1.ru", AF_UNSPEC, 80, TResolveInfo::GENERAL};
    TResolveInfo Host2Info_{"test.host2.ru", AF_UNSPEC, 81, TResolveInfo::GENERAL};
    TResolveInfo Host3Info_{"test.host3.ru", AF_UNSPEC, 82, TResolveInfo::GENERAL};
};

Y_UNIT_TEST_SUITE_F(DnsItsSwitch, TItsSwitchFixture)
{

Y_UNIT_TEST_F(AsyncIsSet_AsyncIsUsed, TItsSwitchFixture)
{
    Config_.UseAsync = true;
    Recreate();

    EXPECT_CALL(*(AsyncMock_), DoResolve(_, _, _)).Times(1);
    EXPECT_CALL(*(ThreadedMock_), DoResolve(_, _, _)).Times(0);
    EXPECT_CALL(*(Checker_), GetCurrentMode()).Times(1)
            .WillOnce(Return(IModeChecker::ECurrentMode::NotSet));

    auto res = Resolver_->Resolve(Host1Info_, false);
}

Y_UNIT_TEST_F(ThreadedIsSet_ThreadedIsUsed, TItsSwitchFixture)
{
    Config_.UseAsync = false;
    Recreate();

    EXPECT_CALL(*(AsyncMock_), DoResolve(_, _, _)).Times(0);
    EXPECT_CALL(*(ThreadedMock_), DoResolve(_, _, _)).Times(1);
    EXPECT_CALL(*(Checker_), GetCurrentMode()).Times(1)
            .WillOnce(Return(IModeChecker::ECurrentMode::NotSet));

    auto res = Resolver_->Resolve(Host1Info_, false);
}

Y_UNIT_TEST_F(SwitchModeInFileToAsync_AsyncResolverCalled, TItsSwitchFixture)
{
    Config_.UseAsync = false;
    Recreate();

    EXPECT_CALL(*(AsyncMock_), DoResolve(_, _, _)).Times(1);
    EXPECT_CALL(*(ThreadedMock_), DoResolve(_, _, _)).Times(1);

    EXPECT_CALL(*(Checker_), GetCurrentMode()).Times(2)
            .WillOnce(Return(IModeChecker::ECurrentMode::Threaded))
            .WillOnce(Return(IModeChecker::ECurrentMode::Async));

    auto res = Resolver_->Resolve(Host1Info_, true);
    auto res2 = Resolver_->Resolve(Host2Info_, false);
}

Y_UNIT_TEST_F(SameModeReturnedSeveralTimes_SameResolverCalled, TItsSwitchFixture)
{
    Config_.UseAsync = false;
    Recreate();

    EXPECT_CALL(*(AsyncMock_), DoResolve(_, _, _)).Times(3);
    EXPECT_CALL(*(ThreadedMock_), DoResolve(_, _, _)).Times(0);

    EXPECT_CALL(*(Checker_), GetCurrentMode()).Times(3)
            .WillRepeatedly(Return(IModeChecker::ECurrentMode::Async));

    auto res = Resolver_->Resolve(Host1Info_, false);
    auto res2 = Resolver_->Resolve(Host2Info_, false);
    auto res3 = Resolver_->Resolve(Host3Info_, true);
}

Y_UNIT_TEST_F(ModeIsNotSetAtFirstThenSet_ResolverSwitched, TItsSwitchFixture)
{
    Config_.UseAsync = true;
    Recreate();

    EXPECT_CALL(*(AsyncMock_), DoResolve(_, _, _)).Times(1);
    EXPECT_CALL(*(ThreadedMock_), DoResolve(_, _, _)).Times(2);

    EXPECT_CALL(*(Checker_), GetCurrentMode()).Times(3)
            .WillOnce(Return(IModeChecker::ECurrentMode::NotSet))
            .WillRepeatedly(Return(IModeChecker::ECurrentMode::Threaded));

    auto res = Resolver_->Resolve(Host1Info_, true);
    auto res2 = Resolver_->Resolve(Host2Info_, false);
    auto res3 = Resolver_->Resolve(Host3Info_, false);
}

Y_UNIT_TEST_F(DifferentModesAreSet_ResolverSwitchedAsExpected, TItsSwitchFixture)
{
    Config_.UseAsync = false;
    Recreate();

    EXPECT_CALL(*(AsyncMock_), DoResolve(_, _, _)).Times(4);
    EXPECT_CALL(*(ThreadedMock_), DoResolve(_, _, _)).Times(6);

    EXPECT_CALL(*(Checker_), GetCurrentMode()).Times(10)
            .WillOnce(Return(IModeChecker::ECurrentMode::NotSet))
            .WillOnce(Return(IModeChecker::ECurrentMode::NotSet))
            .WillOnce(Return(IModeChecker::ECurrentMode::Threaded))
            .WillOnce(Return(IModeChecker::ECurrentMode::Threaded))
            .WillOnce(Return(IModeChecker::ECurrentMode::Async))
            .WillOnce(Return(IModeChecker::ECurrentMode::Threaded))
            .WillOnce(Return(IModeChecker::ECurrentMode::Threaded))
            .WillOnce(Return(IModeChecker::ECurrentMode::Async))
            .WillOnce(Return(IModeChecker::ECurrentMode::Async))
            .WillOnce(Return(IModeChecker::ECurrentMode::Async));

    for (int i = 0; i < 3; ++i) {
        auto res = Resolver_->Resolve(Host1Info_, false);
        auto res2 = Resolver_->Resolve(Host2Info_, true);
        auto res3 = Resolver_->Resolve(Host3Info_, false);
    }
    auto res = Resolver_->Resolve(Host3Info_, false);
}

Y_UNIT_TEST_F(ResetCacheCalled_CacheResetedForAllResolvers, TItsSwitchFixture)
{
    Config_.UseAsync = true;
    Recreate();

    EXPECT_CALL(*(AsyncMock_), DoResetCache()).Times(1);
    EXPECT_CALL(*(ThreadedMock_), DoResetCache()).Times(1);

    Resolver_->ResetCache();
}

Y_UNIT_TEST_F(ResetCacheCalledBetweenResolves_CacheResetedForAllResolversEachTime, TItsSwitchFixture)
{
    Config_.UseAsync = true;
    Recreate();

    EXPECT_CALL(*(AsyncMock_), DoResetCache()).Times(1);
    EXPECT_CALL(*(ThreadedMock_), DoResetCache()).Times(1);

    Resolver_->ResetCache();

    EXPECT_CALL(*(AsyncMock_), DoResolve(_, _, _)).Times(1);
    EXPECT_CALL(*(ThreadedMock_), DoResolve(_, _, _)).Times(2);

    EXPECT_CALL(*(Checker_), GetCurrentMode()).Times(3)
            .WillOnce(Return(IModeChecker::ECurrentMode::NotSet))
            .WillRepeatedly(Return(IModeChecker::ECurrentMode::Threaded));

    auto res = Resolver_->Resolve(Host1Info_, false);
    auto res2 = Resolver_->Resolve(Host2Info_, true);
    auto res3 = Resolver_->Resolve(Host3Info_, false);

    EXPECT_CALL(*(AsyncMock_), DoResetCache()).Times(2);
    EXPECT_CALL(*(ThreadedMock_), DoResetCache()).Times(2);

    Resolver_->ResetCache();
    Resolver_->ResetCache();
}

} // ens of fixture pack

}