#pragma once

#include "exceptionless.h"

#include <balancer/kernel/memory/chunks.h>
#include <library/cpp/http/misc/httpcodes.h>
#include <util/generic/overloaded.h>

#include <util/generic/maybe.h>
#include <util/generic/yexception.h>
#include <util/network/socket.h>
#include <util/string/builder.h>
#include <util/string/cast.h>

#include <variant>

namespace NSrvKernel {
    struct TErrno {
        const int E;

        TErrno(int e) noexcept
            : E(e)
        {}
    };

    // Get error message or ERRNO from TError.
    // See GetErrorMessage
    template <class... Es>
    TString GetShortErrorMessage(const NNoexcept::TError<Es...>& error) noexcept {
        Y_ASSERT(error);
        if (const auto* sysErr = error.template GetAs<TSystemError>()) {
            return TStringBuilder() << TErrno(sysErr->Status());
        }
        return GetErrorMessage(error);
    }

    // Used with MakeError when we didn't catch descendant of std::exception.
    class TNotExceptionError : public yexception {};

    class THttpError : public yexception {
    public:
        THttpError(ui16 code, TString reason = TString()) noexcept
            : Code_(code), Reason_(std::move(reason))
        {
            *this << code << ' ';
            if (Reason_) {
               *this << Reason_ << ' ';
            }
        }

        ui16 Code() const noexcept {
            return Code_;
        }

        TStringBuf Reason() const noexcept {
            return Reason_;
        }

    private:
        ui16 Code_;
        TString Reason_;
    };

    // If we will inherit THttpError casting to error
    // will match both classes
    class TOnStatusCodeError : public yexception {
    public:
        explicit TOnStatusCodeError(ui16 code) noexcept
        : Code_(code)
            {
                *this << code << ' ';
            }

        ui16 Code() const noexcept {
            return Code_;
        }

    private:
        ui16 Code_;
    };

    class THttpParseError : public THttpError {
    public:
        using THttpError::THttpError;

        THttpParseError(const TChunkList& lst, ui16 code = HTTP_BAD_REQUEST) noexcept
            : THttpError(code)
        {
            AppendChunks(lst.Copy());
        }

        THttpParseError(THttpParseError&&) noexcept = default;
        THttpParseError& operator=(THttpParseError&&) noexcept = default;

        THttpParseError(const THttpParseError& f) noexcept
            : THttpParseError(f.Chunks(), f.Code())
        {}

        const TChunkList& Chunks() const noexcept {
            return Chunks_;
        }

        void AppendChunks(const TChunkList& lst) noexcept {
            Chunks_.Append(lst.Copy());
        }

    private:
        TChunkList Chunks_;
    };

    struct THttpParseIncompleteInputError : public THttpParseError {
        THttpParseIncompleteInputError(const TChunkList& lst, ui16 code = HTTP_BAD_REQUEST)
            : THttpParseError(lst, code)
        {}
    };

    class TForceStreamClose : public yexception {};

    class TTouchedOutputError : public yexception {
    public:
        TTouchedOutputError(size_t hedgedIndex)
            : HedgedIndex_(hedgedIndex)
        {}

        size_t HedgedIndex() const {
            return HedgedIndex_;
        }

    private:
        size_t HedgedIndex_ = 0;
    };

    class TAttemptLimitedError : public yexception {
    public:
        TAttemptLimitedError(size_t attempt)
            : Attempt_(attempt)
        {}

        size_t Attempt() const noexcept {
            return Attempt_;
        }

    private:
        size_t Attempt_ = 0;
    };

    class TNoValidBackendsError : public yexception {};

    class TAttemptsOverError : public yexception {};

    class THTTP2Error : public yexception {
    public:
        enum class EErrorType {
            Connection,
            Stream
        };

        enum class EErrorCode : ui32 {
            NO_ERROR = 0x0,
            PROTOCOL_ERROR = 0x1,
            INTERNAL_ERROR = 0x2,
            FLOW_CONTROL_ERROR = 0x3,
            SETTINGS_TIMEOUT = 0x4,
            STREAM_CLOSED = 0x5,
            FRAME_SIZE_ERROR = 0x6,
            REFUSED_STREAM = 0x7,
            CANCEL = 0x8,
            COMPRESSION_ERROR = 0x9,
            CONNECT_ERROR = 0xA,
            ENHANCE_YOUR_CALM = 0xB,
            INADEQUATE_SECURITY = 0xC,
            HTTP_1_1_REQUIRED = 0xD,
            UNKNOWN,
        };

        enum class EFlowControlError : ui32 {
            WindowOverflow,
            ConsumeUnderflow,
            UNKNOWN,
        };

        using TUnspecifiedReason = std::monostate;

    public:
        explicit THTTP2Error(EErrorCode errorCode, EErrorType errorType) noexcept
            : ErrorCode(errorCode)
            , ErrorType(errorType)
        {
            Append("errorType=");
            Append(ErrorType);
            Append(", errorCode=");
            Append(ErrorCode);
        }

        EErrorCode ErrorCode;
        EErrorType ErrorType;
    };

    class THTTP2ConnectionError : public THTTP2Error {
    public:
        enum class ENoError : ui32 {
            Shutdown,
            CpuLimit,
            ConnClose,
            UNKNOWN,
        };

        enum class EProtocolError : ui32 {
            InvalidPreface,
            InvalidFrame,
            UnexpectedFrame,
            InvalidSetting,
            TooManyContinuations,
            TooBigHeaders,
            TooManyHeaders,
            ExpectedSpecHeaders,
            UnexpectedSpecHeaders,
            UnexpectedEOF,
            ZeroWindowUpdate,
            InvalidPriority,
            UNKNOWN,
        };

        enum class EInternalError : ui32 {
            UnexpectedData,
            UnexpectedClose,
            EmptyBackendError,
            RecursiveBackendError,
            StreamNotClosed,
            UnknownError,
            UNKNOWN,
        };

        enum class EStreamClosed : ui32 {
            ClosedByClient,
            UNKNOWN,
        };

        enum class ECompressionError : ui32 {
            InvalidIndex,
            HeaderBlockEnd,
            InvalidInteger,
            InvalidHuffman,
            InvalidSizeUpdate,
            InvalidHeaderFieldType,
            UNKNOWN,
        };

        using TErrorReason = std::variant<
            TUnspecifiedReason,
            ENoError,
            EProtocolError,
            EInternalError,
            EFlowControlError,
            EStreamClosed,
            ECompressionError
        >;

    public:
        explicit THTTP2ConnectionError(EErrorCode errorCode, TErrorReason reason) noexcept
            : THTTP2Error(errorCode, EErrorType::Connection)
            , ErrorReason(std::move(reason))
        {
            Append(", reason=");
            Append(ErrorReason);
            Append(", what=");
        }

        TErrorReason ErrorReason;
    };

    inline auto CompressionError(THTTP2ConnectionError::TErrorReason reason) {
        return THTTP2ConnectionError(THTTP2ConnectionError::EErrorCode::COMPRESSION_ERROR, reason);
    }

    class THTTP2StreamError : public THTTP2Error {
    public:
        enum class ENoError : ui32 {
            ResponseFinished,
            UNKNOWN,
        };

        enum class EProtocolError : ui32 {
            InvalidPriority,
            EmptyHeaderName,
            BadName,
            BadValue,
            MisplacedSpecHeader,
            DuplicateHeader,
            ProhibitedHeader,
            ProhibitedHeaderValue,
            NoMethod,
            NoScheme,
            NoPath,
            NoHost,
            BadPath,
            DuplicateHost,
            BadContentLength,
            Http4xx,
            TooBigHeaders,
            TooManyHeaders,
            ContentLengthExceeded,
            ContentLengthNotMatched,
            ZeroWindowUpdate,
            UNKNOWN,
        };

        enum class EInternalError : ui32 {
            TrailersNotImplemented,
            ForceStreamClose,
            BackendError,
            ResponseSendError,
            UnknownError,
            UNKNOWN,
        };

        enum class EStreamClosed : ui32 {
            AlreadyErased,
            UNKNOWN,
        };

        enum class ERefusedStream : ui32 {
            BackendError,
            MaxStreams,
            Shutdown,
            UNKNOWN,
        };

        using TErrorReason = std::variant<
            TUnspecifiedReason,
            ENoError,
            EProtocolError,
            EInternalError,
            EFlowControlError,
            EStreamClosed,
            ERefusedStream
        >;

    public:
        explicit THTTP2StreamError(EErrorCode errorCode, TErrorReason reason) noexcept
            : THTTP2Error(errorCode, EErrorType::Stream)
            , ErrorReason(std::move(reason))
        {
            Append(", reason=");
            Append(ErrorReason);
            Append(", what=");
        }

        TErrorReason ErrorReason;
    };

    class TPingFailedException : public yexception {};
    class TCoroutineCanceledException : public yexception {};
    class TRecvTimeoutError : public yexception {};

    class TSslErrorStatus {
    public:
        TSslErrorStatus(int status) noexcept
            : Status_(status) {}

        int Status() const noexcept {
            return Status_;
        }

    private:
        int Status_;
    };

    class TSslError : public TSslErrorStatus, public yexception {
    public:
        TSslError(int status) noexcept
            : TSslErrorStatus(status) {}
    };

    class TStreamError : public yexception {
    public:
        explicit TStreamError(size_t unreadLength, size_t totalLength) noexcept
            : UnreadLength(unreadLength)
        {
            (*this) << "EOF after " << (totalLength - unreadLength)
                << " of " << totalLength << " bytes ";

        }

        size_t GetUnreadLength() const noexcept {
            return UnreadLength;
        }

    private:
        size_t UnreadLength = 0;
    };
}

const TString& ToString(NSrvKernel::THTTP2Error::EErrorType);
const TString& ToString(NSrvKernel::THTTP2Error::EErrorCode);
const TString& ToString(NSrvKernel::THTTP2Error::EFlowControlError);
const TString& ToString(NSrvKernel::THTTP2ConnectionError::ENoError);
const TString& ToString(NSrvKernel::THTTP2ConnectionError::EProtocolError);
const TString& ToString(NSrvKernel::THTTP2ConnectionError::EInternalError);
const TString& ToString(NSrvKernel::THTTP2ConnectionError::EStreamClosed);
const TString& ToString(NSrvKernel::THTTP2ConnectionError::ECompressionError);
const TString& ToString(NSrvKernel::THTTP2StreamError::ENoError);
const TString& ToString(NSrvKernel::THTTP2StreamError::EProtocolError);
const TString& ToString(NSrvKernel::THTTP2StreamError::EInternalError);
const TString& ToString(NSrvKernel::THTTP2StreamError::EStreamClosed);
const TString& ToString(NSrvKernel::THTTP2StreamError::ERefusedStream);

namespace NSrvKernel {

class TThreadedFileError : public yexception {
public:
    TThreadedFileError() noexcept {
        Append("(threaded file) ");
    }
};

class TResourceBusyError : public yexception {};

class TDnsError: public yexception {};

class TStoreException: public yexception {};

class TBadMessageError : public yexception {};

class TZeroLengthMessage : public TBadMessageError {};

class TIncompleteMessage : public TBadMessageError {};

class TBanAddressError : public yexception {};

class TBackendError;
class TConnectError;

using TError = NNoexcept::TError<
    std::length_error,
    yexception,
    TStoreException,
    TDateTimeParseException,
    TFileError,
    TBackendError,
    TBadMessageError,
    TCoroutineCanceledException,
    TForceStreamClose,
    THTTP2Error,
    THTTP2ConnectionError,
    THTTP2StreamError,
    THttpError,
    THttpParseError,
    THttpParseIncompleteInputError,
    TIncompleteMessage,
    TNetworkResolutionError,
    TNotExceptionError,
    TPingFailedException,
    TRecvTimeoutError,
    TSslError,
    TStreamError,
    TSystemError,
    TThreadedFileError,
    TZeroLengthMessage,
    TFromStringException,
    TOnStatusCodeError,
    TAttemptLimitedError,
    TNoValidBackendsError,
    TAttemptsOverError,
    TTouchedOutputError,
    TDnsError,
    TResourceBusyError,
    TConnectError,
    TBanAddressError
    // to be expanded
>;

class TBackendError : public yexception {
public:
    explicit TBackendError(TString errorMessage)
        : InnerError_(MakeHolder<TError>(Y_MAKE_ERROR(yexception{} << errorMessage)))
        , ErrorMessage_(std::move(errorMessage))
    {}

    TBackendError(const TBackendError& other);

    TBackendError(TError&& error);

    TBackendError(TBackendError&& other) noexcept = default;

    TBackendError& operator=(TBackendError&&) noexcept = default;

    TBackendError(TError&& error, bool fastError);

    void StoreTo(TError& error) noexcept;

    const TError& InnerError() const noexcept;

    [[nodiscard]] const TMaybe<bool> FastError() const noexcept {
        return FastError_;
    }

    const TString& ErrorMessage() const {
        return ErrorMessage_;
    }

private:
    TMaybe<bool> FastError_;
    THolder<TError> InnerError_;
    TString ErrorMessage_;
};

class TConnectError: public TBackendError {
  public:
    using TBackendError::TBackendError;
};

template <class T>
using TErrorOr = NNoexcept::TErrorOr<T, TError>;

template <class TErr, class TGet, class TCode>
inline bool ErrorHasCode(const TError& err, TGet&& get, std::initializer_list<TCode> codes) {
    if (!err) {
        return false;
    }
    auto tErr = err.GetAs<TErr>();
    return tErr && IsIn(codes, get(*tErr));
}

inline bool ErrorHasErrno(const TError& err, std::initializer_list<int> codes) {
    return ErrorHasCode<TSystemError>(err, [](const TSystemError& e) {
        return e.Status();
    }, codes);
}

template <class TCode>
inline bool ErrorHasHttpCode(const TError& err, std::initializer_list<TCode> codes) {
    return ErrorHasCode<THttpError>(err, [](const THttpError& e) {
        return e.Code();
    }, codes);
}

bool ErrorIsConnRefused(int err);

bool ErrorIsConnRefused(const TError& err);

}  // namespace NSrvKernel
