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

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

using namespace NSrvKernel;

namespace {

TErrorOr<bool> CreateIf(bool create) noexcept {
    if (create) {
        return Y_MAKE_ERROR(yexception{} << "ERROR");
    } else {
        return true;
    }
}

struct TExceptionMock : public yexception {
    TExceptionMock(int status)
        : Status(status)
    {
        ++CtorCalls;
    }

    TExceptionMock(const TExceptionMock& rhs)
        : Status(rhs.Status)
    {
        ++CopyCalls;
    }

    TExceptionMock(TExceptionMock&& rhs)
        : Status(rhs.Status)
    {
        ++MoveCalls;
    }

    TExceptionMock& operator=(const TExceptionMock& rhs) {
        Status = rhs.Status;
        ++CopyCalls;
        return *this;
    }

    TExceptionMock& operator=(TExceptionMock&& rhs) {
        Status = rhs.Status;
        ++MoveCalls;
        return *this;
    }

    ~TExceptionMock() {
        ++DtorCalls;
    }

    int Status;

    static int CtorCalls;
    static int DtorCalls;
    static int MoveCalls;
    static int CopyCalls;

    static void ResetStats() noexcept {
        CtorCalls = DtorCalls = MoveCalls = CopyCalls = 0;
    }
};

int TExceptionMock::CtorCalls = 0;
int TExceptionMock::DtorCalls = 0;
int TExceptionMock::MoveCalls = 0;
int TExceptionMock::CopyCalls = 0;

constexpr int STATUS_CODE = 5;

constexpr char SAMPLE_STRING[] = " ErrorText ";

}  // namespace

Y_UNIT_TEST_SUITE(THelpersExceptionlessUnitTest) {
    Y_UNIT_TEST(MakeErrorTest) {
        auto yerror = Y_MAKE_ERROR(yexception{} << "text");
        UNIT_ASSERT(yerror);
        UNIT_ASSERT(TString(yerror->what()).EndsWith("text"));
        UNIT_ASSERT_UNEQUAL(yerror.GetAs<std::exception>(), nullptr);
        UNIT_ASSERT_UNEQUAL(yerror.GetAs<yexception>(), nullptr);
        UNIT_ASSERT_EQUAL(yerror.GetAs<TSystemError>(), nullptr);
        try {
            yerror.Throw();
        } catch (const yexception& e) {
            UNIT_ASSERT(TString(e.what()).EndsWith("text"));
        } catch (...) {
            UNIT_FAIL("Exception wasn't captured");
        }

        auto systemError = Y_MAKE_ERROR(TSystemError{ STATUS_CODE });
        UNIT_ASSERT_UNEQUAL(systemError.GetAs<std::exception>(), nullptr);
        UNIT_ASSERT_UNEQUAL(systemError.GetAs<yexception>(), nullptr);
        UNIT_ASSERT_UNEQUAL(systemError.GetAs<TSystemError>(), nullptr);
        UNIT_ASSERT_EQUAL(systemError.GetAs<TExceptionMock>(), nullptr);
        UNIT_ASSERT_EQUAL(systemError.GetAs<TSystemError>()->Status(), 5);
        try {
            systemError.Throw();
        } catch (const TSystemError& e) {
            UNIT_ASSERT_EQUAL(e.Status(), STATUS_CODE);
        } catch (...) {
            UNIT_FAIL("Exception wasn't captured");
        }
    }

    Y_UNIT_TEST(TErrorOrTest) {
        auto mustBeError = CreateIf(true);
        UNIT_ASSERT(mustBeError);
        auto error = mustBeError.ReleaseError();
        UNIT_ASSERT(error);
        UNIT_ASSERT(TString(error->what()).EndsWith("ERROR"));

        auto mustBeBool = CreateIf(false);
        bool res;
        UNIT_ASSERT(!mustBeBool);
        error = mustBeBool.AssignTo(res);
        UNIT_ASSERT(!error);
        UNIT_ASSERT(res);
    }

    Y_UNIT_TEST(Y_TRY_WithTErrorTest) {
        auto error = []() -> TError {
            Y_TRY(TError, error) {
                return Y_MAKE_ERROR(TSystemError{ STATUS_CODE });
            } Y_CATCH {
                UNIT_ASSERT(error);
                UNIT_ASSERT_UNEQUAL(error.GetAs<TSystemError>(), nullptr);
                UNIT_ASSERT_EQUAL(error.GetAs<TSystemError>()->Status(), STATUS_CODE);
                return {};
            }
            return {};
        }();
        UNIT_ASSERT(!error);

        error = []() -> TError {
            Y_TRY(TError, error) {
                return Y_MAKE_ERROR(TSystemError{ STATUS_CODE });
            } Y_CATCH {
                UNIT_ASSERT(error);
                UNIT_ASSERT_UNEQUAL(error.GetAs<TSystemError>(), nullptr);
                UNIT_ASSERT_EQUAL(error.GetAs<TSystemError>()->Status(), STATUS_CODE);
                return error;
            }
            return {};
        }();
        UNIT_ASSERT(error);
        UNIT_ASSERT_UNEQUAL(error.GetAs<TSystemError>(), nullptr);
        UNIT_ASSERT_EQUAL(error.GetAs<TSystemError>()->Status(), STATUS_CODE);
    }

    Y_UNIT_TEST(Y_TRY_STORE_TYPE_FOUND) {
        /**
         * testing that Y_TRY_STORE casts exception to TError of corresponding type,
         * if parantheses contain that type.
         **/
        auto error = []() -> TError {
            try {
                ythrow TSystemError{ STATUS_CODE } << SAMPLE_STRING;
            } Y_TRY_STORE(THttpError, TSystemError, yexception)
            return {};
        }();

        UNIT_ASSERT(error);
        UNIT_ASSERT_EQUAL(error.GetAs<THttpError>(), nullptr);
        UNIT_ASSERT_EQUAL(error.GetAs<TSystemError>()->Status(), STATUS_CODE);
        UNIT_ASSERT_UNEQUAL(TString(error->what()).find(SAMPLE_STRING), TString::npos);
    }

    Y_UNIT_TEST(Y_TRY_STORE_TYPE_NOT_FOUND_YEXCEPTION_DESCENDANT) {
        /**
         * if parantheses ain't contain type that exception can be casted to
         * and exception is yexception descendant Y_TRY_STORE casts exception
         * to TError of yexception.
         **/
        auto error = []() -> TError {
            try {
                ythrow TSystemError{ STATUS_CODE } << SAMPLE_STRING;
            } Y_TRY_STORE(THttpError, TBackendError, yexception)
            return {};
        }();

        UNIT_ASSERT(error);
        UNIT_ASSERT_EQUAL(error.GetAs<THttpError>(), nullptr);
        UNIT_ASSERT_EQUAL(error.GetAs<TBackendError>(), nullptr);
        UNIT_ASSERT_UNEQUAL(error.GetAs<yexception>(), nullptr);
        UNIT_ASSERT_UNEQUAL(TString(error->what()).find(SAMPLE_STRING), TString::npos);
    }

    Y_UNIT_TEST(Y_TRY_STORE_TYPE_NOT_FOUND_STDEXCEPTION_DESCENDANT) {
        /**
         * if parantheses ain't contain type that exception can be casted to
         * and exception is std::exception descendant Y_TRY_STORE casts exception
         * to TError of yexception and writes what() in it.
         **/
        auto error = []() -> TError {
            try {
                throw std::length_error(SAMPLE_STRING);
            } Y_TRY_STORE(THttpError, TBackendError, yexception)
            return {};
        }();

        UNIT_ASSERT(error);
        UNIT_ASSERT_EQUAL(error.GetAs<THttpError>(), nullptr);
        UNIT_ASSERT_EQUAL(error.GetAs<TBackendError>(), nullptr);
        UNIT_ASSERT_UNEQUAL(error.GetAs<yexception>(), nullptr);
        UNIT_ASSERT_UNEQUAL(TString(error->what()).find(SAMPLE_STRING), TString::npos);
    }

    Y_UNIT_TEST(Y_TRY_STORE_TYPE_TYPE_ORDER) {
        /**
         * testing that Y_TRY_STORE casts exception to TError of first type exception can be casted to
         **/
        auto error = []() -> TError {
            try {
                ythrow TIncompleteMessage{} << SAMPLE_STRING;
            } Y_TRY_STORE(TBadMessageError, TIncompleteMessage, yexception)
            return {};
        }();

        UNIT_ASSERT(error);
        UNIT_ASSERT_EQUAL(error.GetAs<TIncompleteMessage>(), nullptr);
        UNIT_ASSERT_UNEQUAL(error.GetAs<TBadMessageError>(), nullptr);
        UNIT_ASSERT_UNEQUAL(TString(error->what()).find(SAMPLE_STRING), TString::npos);
    }

    Y_UNIT_TEST(ErrorHasErrno) {
        UNIT_ASSERT(ErrorHasErrno(Y_MAKE_ERROR(TSystemError(EIO)), {EIO}));
        UNIT_ASSERT(!ErrorHasErrno(Y_MAKE_ERROR(TSystemError(EIO)), {ECANCELED}));
        UNIT_ASSERT(ErrorHasErrno(Y_MAKE_ERROR(TSystemError(EIO)), {EIO, ECANCELED}));
        UNIT_ASSERT(ErrorHasErrno(Y_MAKE_ERROR(TSystemError(ECANCELED)), {EIO, ECANCELED}));

        UNIT_ASSERT(!ErrorHasErrno(Y_MAKE_ERROR(THttpError(500)), {500}));
    }

    Y_UNIT_TEST(ErrorHasHttpCode) {
        UNIT_ASSERT(ErrorHasHttpCode(Y_MAKE_ERROR(THttpError(500)), {500}));
        UNIT_ASSERT(!ErrorHasHttpCode(Y_MAKE_ERROR(THttpError(500)), {501}));
        UNIT_ASSERT(ErrorHasHttpCode(Y_MAKE_ERROR(THttpError(500)), {500, 501}));
        UNIT_ASSERT(ErrorHasHttpCode(Y_MAKE_ERROR(THttpError(501)), {500, 501}));

        UNIT_ASSERT(!ErrorHasHttpCode(Y_MAKE_ERROR(TSystemError(EIO)), {500}));
    }
}
