#include "buffer.h"
#include "http.h"

#include <util/generic/scope.h>

namespace {
    static const NSv::THead FakeRequest = {"CONNECT", "localhost", {{":authority", "unknown"}, {":scheme", "tcp"}}};
    static const NSv::THead FakeResponse = 200;

    struct TRawTCPStream : NSv::IStream {
    private:
        NSv::IO& IO;
        NSv::TLogFrame Log_;
        NSv::THead Head_;
        NSv::THeaderVector Tail_;
        NSv::TBuffer Buffer;
        cone::guard Reader;
        size_t Consumed = 0;
        bool Written = false;
        bool Server = false;

    public:
        TRawTCPStream(NSv::IO& io, NSv::TLogFrame log, bool server)
            : IO(io)
            , Log_(std::move(log))
            , Head_(server ? FakeRequest : FakeResponse)
            , Server(server)
        {
            Reader = [this, parent = server ? ::cone : nullptr] {
                Y_DEFER {
                    if (parent) {
                        parent->cancel();
                    }
                };
                char buf[8192];
                while (auto rd = IO.ReadInto({buf, sizeof(buf)})) {
                    if (!*rd) {
                        Buffer.Close();
                        return true;
                    }
                    if (!Buffer.Write({buf, *rd}) MUN_RETHROW) {
                        return false;
                    }
                }
                Buffer.Abort();
                return mun_errno == ECONNRESET;
            };
        }

        NSv::IP Peer() const noexcept override {
            return IO.Peer();
        }

        NSv::TLogFrame& Log() noexcept override {
            return Log_;
        }

        NSv::THead* Head() noexcept override {
            return &Head_;
        }

        NSv::THeaderVector* Tail() noexcept override {
            return &Tail_;
        }

        TMaybe<TStringBuf> Read() noexcept override {
            return Buffer.Read();
        }

        bool Consume(size_t n) noexcept override {
            Consumed += n;
            return Buffer.Consume(n);
        }

        bool AtEnd() const noexcept override {
            return Buffer.AtEnd();
        }

        size_t Replay() noexcept override {
            return !Written ? Consumed : -1;
        }

        bool WriteHead(NSv::THead&) noexcept override {
            Written = true;
            return true;
        }

        bool Write(TStringBuf buf) noexcept override {
            // FIXME should check that `WriteHead` was called with a non-informational code
            Written = true;
            while (buf) {
                auto wr = IO.Write(buf); // XXX maybe use a TBuffer?
                if (!wr) {
                    return false;
                }
                buf.Skip(*wr);
            }
            return true;
        }

        bool Close(NSv::THeaderVector&) noexcept override {
            // FIXME should check that `WriteHead` was called with a non-informational code
            // XXX should do a one-sided shutdown? it's kinda-not-really working...
            return !Server || !mun_error(EREQDONE, "done handling request");
        }
    };

    struct TRawTCPServer : NSv::IConnection {
    private:
        cone::guard Handler_;

    public:
        TRawTCPServer(NSv::IO& io, NSv::THandler h, NSv::TLogFrame log) {
            Handler_ = [&io, h = std::move(h), log = std::move(log)]() mutable {
                auto stream = std::make_shared<TRawTCPStream>(io, std::move(log), true);
                if (h(stream)) {
                    mun_error(EINVAL, "handler finished without closing the stream");
                }
                if (!EqualToOneOf(mun_errno, ECANCELED, EREQDONE) MUN_RETHROW) {
                    stream->Log().Push<NSv::NEv::THxHandlerError>(mun_errno, mun_last_error()->text);
                    return false;
                }
                return true;
            };
        }

        bool IsIdle() const noexcept override {
            return false;
        }
        bool Wait() noexcept override {
            return Handler_->wait(cone::norethrow);
        }
        bool Shutdown() noexcept override {
            return true;
        }
        bool Migrate() noexcept override {
            return true;
        }

        NSv::IStreamPtr Request(const NSv::THead&, bool, NSv::TLogFrame) noexcept override {
            return mun_error(EINVAL, "this is a server, it cannot send requests"), nullptr;
        }
    };

    struct TRawTCPClient : NSv::IConnection {
    private:
        NSv::IO* IO = nullptr;

    public:
        TRawTCPClient(NSv::IO& io)
            : IO(&io)
        {}

        bool IsIdle() const noexcept override {
            return !!IO;
        }
        bool Wait() noexcept override {
            return true;
        }
        bool Shutdown() noexcept override {
            return true;
        }
        bool Migrate() noexcept override {
            return true;
        }

        NSv::IStreamPtr Request(const NSv::THead&, bool, NSv::TLogFrame log) noexcept override {
            if (!IO) {
                return mun_error(EPIPE, "this connection is already in use"), nullptr;
            }
            auto stream = std::make_shared<TRawTCPStream>(*IO, std::move(log), false);
            IO = nullptr;
            return stream;
        }
    };
}

THolder<NSv::IConnection> NSv::RawTCPServer(NSv::IO& io, NSv::THandler h, NSv::TLogFrame log) {
    return MakeHolder<TRawTCPServer>(io, std::move(h), std::move(log));
}

THolder<NSv::IConnection> NSv::RawTCPClient(NSv::IO& io) {
    return MakeHolder<TRawTCPClient>(io);
}
