#pragma once

#include <balancer/client/experimental/base/requester.h>
#include <balancer/client/experimental/base/request_context.h>
#include <balancer/client/experimental/base/request.h>
#include <balancer/client/experimental/base/response_parser.h>
#include <balancer/client/experimental/base/response_stream.h>
#include <balancer/client/experimental/base/timeout_error.h>

namespace NHttp {
class TRetrier: public IRequester {
    class TRequestContext: public IRequestContext {
        class TResponseParser: public IResponseParser {
            class TResponseStream: public IResponseStream {
              public:
                TResponseStream(IResponseStream& subStream): SubStream_{subStream} {}

                void Read(size_t sizeLimit, bool peek, TInstant deadline, TReadHandler callback) noexcept override {
                    SubStream_.Read(sizeLimit, peek, deadline, [callback = std::move(callback)] (auto result) {
                        if (result.IsSuccess() && result.Success().Defined()) {
                            if (auto head = std::get_if<TResponseHeadFrame>(result.Success().Get())) {
                                if (head->StatusCode != 200) {
                                    TStringBuilder error;
                                    error << "status code " << head->StatusCode;
                                    return callback(TError{std::move(error)});
                                }
                            }
                        }

                        return callback(std::move(result));
                    });
                }

              private:
                IResponseStream& SubStream_;
            };
          public:
            TResponseParser(IResponseParser& subParser): SubParser_{subParser} {}

            void Parse(IResponseStream& subStream, TInstant deadline, THandler callback) noexcept override {
                TResponseStream stream{subStream};
                SubParser_.Parse(stream, deadline, callback);
            }

          private:
            IResponseParser& SubParser_;
        };

      public:
        TRequestContext(IRequester& subRequester, TRequest request, ui32 retries): SubRequester_{subRequester}, Request_{std::move(request)}, Retries_{retries} {}

        void Cancel() noexcept override {
            if (AttemptContext_) {
                AttemptContext_->Cancel();
            }
        }

        void Write(TMaybe<TRequestFrame> frame) noexcept override {
            // TODO: копить данные
            if (AttemptContext_) {
                AttemptContext_->Write(std::move(frame));
            }
        }

        void Read(IResponseParser& nestedParser, TInstant deadline, TReadHandler callback) noexcept override {
            // TODO: parallel reads
            // TODO: thread safety
            if (!AttemptContext_) {
                if (!Retries_) {
                    return callback(LastError_);
                }
                Retries_--;

                AttemptContext_ = SubRequester_.Send(Request_);
            }

            // TODO: в начале отдать накопленное
            TResponseParser parser{nestedParser};
            AttemptContext_->Read(parser, deadline, [this, &nestedParser, deadline, callback = std::move(callback)](auto result) mutable {
                // TODO: проверять, что не отдали данных наружу
                if (result.IsError()) {
                    LastError_ = std::move(result.Error());
                    if (LastError_.GetInfo<TTimeoutError>()) {
                        Retries_ = 0;
                        return callback(LastError_);
                    }
                    AttemptContext_.reset();
                    return Read(nestedParser, deadline, std::move(callback));
                }

                return callback(std::move(result));
            });
        }

      private:
        IRequester& SubRequester_;
        TRequest Request_;
        ui32 Retries_;
        std::unique_ptr<IRequestContext> AttemptContext_;
        TError LastError_ = TError{"out of budget"};
    };

  public:
    TRetrier(IRequester& subRequester, ui32 retries): SubRequester_{subRequester}, Retries_{retries} {}

    std::unique_ptr<IRequestContext> Send(TRequest request) noexcept override {
        return std::make_unique<TRequestContext>(SubRequester_, std::move(request), Retries_);
    }

  private:
    IRequester& SubRequester_;
    const ui32 Retries_;
};
}
