#ifndef TILE_NET_HTTP_HTTP_CLIENT_H
#define TILE_NET_HTTP_HTTP_CLIENT_H

#pragma once

#include "tile/base/expected.h"
#include "tile/base/future.h"
#include "tile/base/internal/lazy_init.h"
#include "tile/net/http/http_request.h"
#include "tile/net/http/http_response.h"

#include "gflags/gflags_declare.h"

#include <ostream>

DECLARE_int32(tile_http_client_default_timeout_ms);

namespace tile {
class HttpClient {
public:
    struct Options {
        bool verify_server_certificate = true;
        bool follow_redirects          = true;
        bool proxy_from_env            = true;

        std::string proxy;
        bool use_bintin_compression = false;
    };

    enum ErrorCode {
        ERROR_INVALID = 0,// unused
        ERROR_INVALID_URI_ADDRESS,
        ERROR_FAIL_TO_RESOLVE_ADDRESS,
        ERROR_FAIL_TO_SEND_REQUEST,
        ERROR_FAIL_TO_GET_RESPONSE,
        ERROR_CONNECTION,
        ERROR_TIMEOUT,
        ERROR_PARSE_RESPONSE,
        ERROR_FAIL_TO_CONNECT_SERVER,
        ERROR_PROTOCOL_NOT_SUPPORTED,
        ERROR_TOO_MANY_REDIRECTS,
        ERROR_REDIRECT_LOCATION_NOT_FOUND,
        ERROR_DECOMPRESS_RESPONSE,
        ERROR_PROXY,
        ERROR_HTTP2,
        ERROR_SSL,
        ERROR_IO,
        ERROR_INTERNAL_ERROR,
        ERROR_DRY_RUN,
        ERROR_GET_NOTHING,
        ERROR_UNKNOWN = 100,
    };

    static const char *ErrorCodeToString(int error_code);

    struct RequestOptions {
        int max_redirection_count        = 1;
        std::chrono::nanoseconds timeout = std::chrono::milliseconds(FLAGS_tile_http_client_default_timeout_ms);
        std::string content_type;        // ex: text:html
        std::vector<std::string> headers;// ex: "Content-Type: text/html"
        bool verbose              = false;
        bool no_automatic_upgrade = false;// use http2 without 1.1  upgrade
        HttpVersion http_version  = HttpVersion::Unspecified;
        std::string override_host_nslb;

        std::string binlog_correlation_id;
    };

    struct ResponseInfo {
        std::string effective_url;                   // get the last used URL.
        std::chrono::nanoseconds total_time_transfer;// transfer time
        HttpVersion http_version;
    };

    explicit HttpClient(const Options &options = internal::LazyInitConstant<Options>());

    Future<std::expected<HttpResponse, ErrorCode>>
    AsyncGet(const std::string &url,
             const RequestOptions &options = internal::LazyInitConstant<RequestOptions>(),
             ResponseInfo *response_detail = nullptr);

    std::expected<HttpResponse, ErrorCode>
    Get(const std::string &url,
        const RequestOptions &options = internal::LazyInitConstant<RequestOptions>(),
        ResponseInfo *response_detail = nullptr);

    Future<std::expected<HttpResponse, ErrorCode>>
    AsyncPost(const std::string &url,
              const std::string &data,
              const RequestOptions &options = internal::LazyInitConstant<RequestOptions>(),
              ResponseInfo *response_detail = nullptr);

    std::expected<HttpResponse, ErrorCode>
    Post(const std::string &url,
         const std::string &data,
         const RequestOptions &options = internal::LazyInitConstant<RequestOptions>(),
         ResponseInfo *response_detail = nullptr);

    // Generic method
    Future<std::expected<HttpResponse, ErrorCode>>
    AsyncRequest(const std::string &protocol,
                 const std::string &host,
                 const HttpRequest &request,
                 const RequestOptions &options = internal::LazyInitConstant<RequestOptions>(),
                 ResponseInfo *response_detail = nullptr);

    std::expected<HttpResponse, ErrorCode>
    Request(const std::string &protocol,
            const std::string &host,
            const HttpRequest &request,
            const RequestOptions &options = internal::LazyInitConstant<RequestOptions>(),
            ResponseInfo *response_detail = nullptr);

private:
    Options options_;
};

namespace detail {
class HttpChannel {
public:
    HttpChannel()                                                                                             = default;
    virtual ~HttpChannel()                                                                                    = default;

    virtual void AsyncGet(const std::string &url,
                          const HttpClient::Options &opts,
                          const HttpClient::RequestOptions &request_options,
                          HttpClient::ResponseInfo *response_info,
                          std::function<void(std::expected<HttpResponse, HttpClient::ErrorCode>)> &&done)     = 0;

    virtual void AsyncPost(const std::string &url,
                           const HttpClient::Options &opts,
                           std::string data,
                           const HttpClient::RequestOptions &request_options,
                           HttpClient::ResponseInfo *response_info,
                           std::function<void(std::expected<HttpResponse, HttpClient::ErrorCode>)> &&done)    = 0;

    virtual void AsyncRequest(const std::string &protocol,
                              const std::string &host,
                              const HttpClient::Options &opts,
                              const HttpRequest &request,
                              const HttpClient::RequestOptions &request_options,
                              HttpClient::ResponseInfo *response_info,
                              std::function<void(std::expected<HttpResponse, HttpClient::ErrorCode>)> &&done) = 0;
};

void RegisterMockHttpChannel(HttpChannel *channel);
void RegisterDryRunHttpChannel(HttpChannel *channel);

}// namespace detail
}// namespace tile

#endif// TILE_NET_HTTP_HTTP_CLIENT_H
