#include "request_context.h"
#include "response_stream.h"
#include "channel_input.h"

#include <balancer/client/experimental/base/response_parser.h>

#include <balancer/kernel/http/parser/request_builder.h>
#include <balancer/modules/balancer/module.h>

#include <library/cpp/threading/future/core/future.h>

namespace {
NSrvKernel::EMethod ConvertMethod(NHttp::EMethod method) {
    switch (method) {
        case NHttp::EMethod::OPTIONS:
            return NSrvKernel::EMethod::OPTIONS;
        case NHttp::EMethod::GET:
            return NSrvKernel::EMethod::GET;
        case NHttp::EMethod::HEAD:
            return NSrvKernel::EMethod::HEAD;
        case NHttp::EMethod::POST:
            return NSrvKernel::EMethod::POST;
        case NHttp::EMethod::PUT:
            return NSrvKernel::EMethod::PUT;
        case NHttp::EMethod::PATCH:
            return NSrvKernel::EMethod::PATCH;
        case NHttp::EMethod::DELETE:
            return NSrvKernel::EMethod::DELETE;
        case NHttp::EMethod::TRACE:
            return NSrvKernel::EMethod::TRACE;
        case NHttp::EMethod::CONNECT:
            return NSrvKernel::EMethod::CONNECT;
        default:
            Y_FAIL("not known method");
    }
}
}

namespace NHttp::NRequesters {
TBalancerClient::TRequestContext::TRequestContext(TBalancerClient& client, TTls& tls, TRequest request)
    : Client_{client}
    , Tls_{tls}
    , InputChannel_{std::numeric_limits<size_t>::max()}
    , Request_{std::move(request)}
    , ResponseStream_{Tls_}
{
}

void TBalancerClient::TRequestContext::Cancel() noexcept {
    Coro_->Cancel();
}

void TBalancerClient::TRequestContext::Write(TMaybe<TRequestFrame> frame) noexcept {
    if (frame.Empty()) {
        Y_VERIFY(InputChannel_.Send(NSrvKernel::TChunkList{}, TInstant::Zero()) == NSrvKernel::EChannelStatus::Success);
        return;
    }

    if (std::holds_alternative<THeadersFrame>(frame.GetRef())) {
        Y_FAIL("not implemented");
    }

    auto data = std::get<TDataFrame>(std::move(frame.GetRef()));
    Y_VERIFY(InputChannel_.Send(std::move(data), TInstant::Zero()) == NSrvKernel::EChannelStatus::Success);
}

void TBalancerClient::TRequestContext::Read(IResponseParser& parser, TInstant deadline, TReadHandler callback) noexcept {
    parser.Parse(ResponseStream_, deadline, std::move(callback));
}

void TBalancerClient::TRequestContext::Run() noexcept {
    Coro_ = Tls_.Create([this]() {
        TChannelInput input{InputChannel_, &Tls_.Executor()};
        auto headOp = [this](NSrvKernel::TResponse&& response, const bool, TInstant) {
            TResponseHeadFrame head;
            head.StatusCode = response.ResponseLine().StatusCode;
            head.ReasonPhrase = response.ResponseLine().Reason.AsString();
            for (auto& header: response.Headers()) {
                for(auto& value: header.second) {
                    head.Headers.AddHeader(header.first.AsString(), value.AsString());
                }
            }
            ResponseStream_.PushData(std::move(head));
            return NSrvKernel::TError{};
        };
        auto bodyOp = [this](NSrvKernel::TChunkList data, TInstant) -> NSrvKernel::TError {
            if (data.Empty()) {
                return {};
            }
            data.MakeOwned();
            ResponseStream_.PushData(std::move(data));
            return {};
        };
        auto trailersOp = [this](NSrvKernel::THeaders&& responseHeaders, TInstant) {
            THttpHeaders headers;
            for (auto& header: responseHeaders) {
                for(auto& value: header.second) {
                    headers.AddHeader(header.first.AsString(), value.AsString());
                }
            }
            ResponseStream_.PushData(std::move(headers));
            return NSrvKernel::TError{};
        };
        NSrvKernel::TAnyHttpOutput<decltype(headOp), decltype(bodyOp), decltype(trailersOp)> output{std::move(headOp),std::move(bodyOp),std::move(trailersOp)};

        Y_VERIFY(Request_.Endpoint.Defined(), "endpoint is empty");
        NSrvKernel::NSrcrwr::TAddrs addrs;
        NSrvKernel::NSrcrwr::TAddr addr;
        addr.Ip = Request_.Endpoint->Ip;
        addr.Port = Request_.Endpoint->Port;
        addrs.SetAddrs({std::move(addr)});

        NSrvKernel::TAddrHolder addrHolder(&NSrvKernel::TDummyAddr::Instance());
        NSrvKernel::TTcpConnProps tcpConnProps(nullptr, addrHolder, addrHolder, nullptr);
        NSrvKernel::TConnProps connProps(tcpConnProps, TInstant::Now(), 0ull);
        connProps.SrcrwrAddrs = &addrs;
        NSrvKernel::TConnDescr descr(input, output, connProps);
        auto builder = NSrvKernel::BuildRequest().Version11()
                .Method(ConvertMethod(Request_.Method))
                .Path(Request_.Path);

        for (auto& header: Request_.Headers) {
            builder.Header(header.Name(), header.Value());
        }

        NSrvKernel::THTTP2StreamDetails http2Details;
        NSrvKernel::TBaseProperties requestProps;

        switch (Request_.ProtocolVersion) {

            case EProtocolVersion::HTTP11:
                requestProps.ClientProto = NSrvKernel::EClientProto::CP_HTTP;
                break;
            case EProtocolVersion::HTTP2:
                requestProps.ClientProto = NSrvKernel::EClientProto::CP_HTTP2;
                requestProps.HTTP2 = &http2Details;
                break;
            default:
                Y_FAIL();
        }

        requestProps.ChunkedTransfer = Request_.ChunkedRequest;
        NSrvKernel::TRequest balancerRequest = builder;
        balancerRequest.Props() = requestProps;

        if (Request_.Query) {
            balancerRequest.RequestLine().CGI = NSrvKernel::TStringStorage{"?" + Request_.Query.Print()};
        }

        descr.Request = &balancerRequest;
        descr.HaveFullBody = !Request_.ChunkedRequest;

        // перед тем, как сделать move, нужно сохранить размер body
        if (descr.HaveFullBody) {
            descr.Request->Props().ContentLength = Request_.Body.size();
        }
        if (!Request_.Body.Empty()) {
            Y_VERIFY(InputChannel_.Send(std::move(Request_.Body), TInstant::Zero()) == NSrvKernel::EChannelStatus::Success);
        }
        // отправка пустого буфера означает конец запроса
        if (descr.HaveFullBody) {
            Y_VERIFY(InputChannel_.Send(NSrvKernel::TChunkList{}, TInstant::Zero()) == NSrvKernel::EChannelStatus::Success);
        }

        NSrvKernel::TAttemptsHolderBase attemptsHolder{0, 0, Request_.Secure};
        descr.AttemptsHolder = &attemptsHolder;
        auto error = Client_.Proxy_.Run(descr, Tls_);
        if (error) {
            return ResponseStream_.Error({TError{NSrvKernel::GetErrorMessage(error)}});
        }
        ResponseStream_.Finish();
    }, "NHttp::TBalancerClient::TRequestContext::Run");
}
}
