/*
 * Copyright (c) 2024, Dan Klishch <danilklishch@gmail.com>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#pragma once

#include <AK/AsyncStream.h>
#include <AK/ByteString.h>
#include <AK/NonnullOwnPtr.h>
#include <AK/OwnPtr.h>
#include <AK/TemporaryChange.h>
#include <AK/Variant.h>
#include <AK/Vector.h>

namespace HTTP {

class Http11Connection;
class Http11Response;

#define ENUMERATE_METHODS(F) \
    F(Invalid)               \
    F(HEAD)                  \
    F(GET)                   \
    F(POST)                  \
    F(DELETE)                \
    F(PATCH)                 \
    F(OPTIONS)               \
    F(TRACE)                 \
    F(CONNECT)               \
    F(PUT)

enum class Method {
#define ID(x) x,
    ENUMERATE_METHODS(ID)
#undef ID
};

struct Header {
    ByteString header;
    ByteString value;
};

struct RequestData {
    struct PlainBody {
        StringView data;
    };

    Method method;
    StringView url;
    Vector<Header> headers;
    Variant<Empty, PlainBody> body = Empty {};
};

class Http11Response final : public StreamWrapper<AsyncInputStream> {
public:
    static Coroutine<ErrorOr<NonnullOwnPtr<Http11Response>>> create(Badge<Http11Connection>, RequestData&& data, AsyncStream& stream);

    u16 status_code() const { return m_status_code; }
    Vector<Header> const& headers() const { return m_headers; }

    AsyncInputStream& body() { return *m_stream; }

private:
    Http11Response(NonnullOwnPtr<AsyncInputStream>&& body, u16 status_code, Vector<Header>&& headers)
        : StreamWrapper(move(body))
        , m_status_code(status_code)
        , m_headers(move(headers))
    {
    }

    u16 m_status_code { 0 };
    Vector<Header> m_headers;
};

class Http11Connection final : public StreamWrapper<AsyncStream> {
public:
    using StreamWrapper::StreamWrapper;

    template<
        typename Func,
        typename T = InvokeResult<Func, Http11Response&>::ReturnType::ResultType>
    Coroutine<ErrorOr<T>> request(RequestData&& data, Func&& func)
    {
        VERIFY(!m_request_in_flight);
        TemporaryChange request_in_flight { m_request_in_flight, true };

        auto response = CO_TRY(co_await Http11Response::create({}, move(data), *m_stream));
        auto result = co_await func(*response);
        if (response->is_open()) {
            auto close_result = co_await response->close();
            if (!result.is_error()) // Preserve callback error in case it has failed.
                CO_TRY(move(close_result));
        }
        co_return result;
    }

private:
    bool m_request_in_flight { false };
};

}
