#pragma once

#include "exthttpcodes.h"

#include <library/cpp/mime/types/mime.h>

#include <util/system/defaults.h>
#include <util/system/compat.h>
#include <util/generic/string.h>
#include <util/generic/ylimits.h>
#include <util/system/maxlen.h>

#include <ctime>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>

// This is ugly solution but here a lot of work to do it the right way.
#define FETCHER_URL_MAX 8192

extern const i64 DEFAULT_RETRY_AFTER;       /// == -1
extern const i64 DEFAULT_IF_MODIFIED_SINCE; /// == -1
extern const i32 DEFAULT_MAX_AGE;           /// == -1
extern const i8 DEFAULT_REQUEST_PRIORITY;   /// == -1
extern const i32 DEFAULT_RESPONSE_TIMEOUT;  /// == -1

#define HTTP_PREFIX "http://"
#define MAX_LANGREGION_LEN 4
#define MAXWORD_LEN 55

enum HTTP_COMPRESSION {
    HTTP_COMPRESSION_UNSET = 0,
    HTTP_COMPRESSION_ERROR = 1,
    HTTP_COMPRESSION_IDENTITY = 2,
    HTTP_COMPRESSION_GZIP = 3,
    HTTP_COMPRESSION_DEFLATE = 4,
    HTTP_COMPRESSION_COMPRESS = 5,
    HTTP_COMPRESSION_MAX = 6
};

enum HTTP_METHOD {
    HTTP_METHOD_UNDEFINED = -1,
    HTTP_METHOD_OPTIONS,
    HTTP_METHOD_GET,
    HTTP_METHOD_HEAD,
    HTTP_METHOD_POST,
    HTTP_METHOD_PUT,
    HTTP_METHOD_DELETE,
    HTTP_METHOD_TRACE,
    HTTP_METHOD_CONNECT,
    HTTP_METHOD_EXTENSION
};

enum HTTP_CONNECTION {
    HTTP_CONNECTION_UNDEFINED = -1,
    HTTP_CONNECTION_KEEP_ALIVE = 0,
    HTTP_CONNECTION_CLOSE = 1
};

/// Class represents general http header fields.
struct THttpBaseHeader {
public:
    i16 error;
    i32 header_size;
    i32 entity_size;
    i64 content_length;
    i64 http_time;                   // seconds since epoch
    i64 content_range_start;         // Content-Range: first-byte-pos
    i64 content_range_end;           // Content-Range: last-byte-pos
    i64 content_range_entity_length; // Content-Range: entity-length
    i8 http_minor;
    i8 mime_type;
    i8 charset;
    i8 compression_method;
    i8 transfer_chunked;
    i8 connection_closed;
    TString base;

public:
    void Init() {
        error = 0;
        header_size = 0;
        entity_size = 0;
        content_length = -1;
        http_time = -1;
        http_minor = -1;
        mime_type = -1;
        charset = -1;
        compression_method = HTTP_COMPRESSION_UNSET;
        transfer_chunked = -1;
        connection_closed = HTTP_CONNECTION_UNDEFINED;
        content_range_start = -1;
        content_range_end = -1;
        content_range_entity_length = -1;
        base.clear();
    }

    void Print() const {
        printf("content_length: %" PRIi64 "\n", content_length);
        printf("http_time: %" PRIi64 "\n", http_time);
        printf("http_minor: %" PRIi8 "\n", http_minor);
        printf("mime_type: %" PRIi8 "\n", mime_type);
        printf("charset: %" PRIi8 "\n", charset);
        printf("compression_method: %" PRIi8 "\n", compression_method);
        printf("transfer_chunked: %" PRIi8 "\n", transfer_chunked);
        printf("connection_closed: %" PRIi8 "\n", connection_closed);
        printf("content_range_start: %" PRIi64 "\n", content_range_start);
        printf("content_range_end: %" PRIi64 "\n", content_range_end);
        printf("content_range_entity_length: %" PRIi64 "\n", content_range_entity_length);
        printf("base: \"%s\"\n", base.c_str());
        printf("error: %" PRIi16 "\n", error);
    }

    int SetBase(const char* path,
                const char* hostNamePtr = nullptr,
                int hostNameLength = 0) {
        if (*path == '/') {
            base = "http://";
            base += TStringBuf(hostNamePtr, hostNameLength);
            base += path;
        } else {
            base = path;
        }
        return error;
    }
};

enum { HREFLANG_MAX = FETCHER_URL_MAX * 2 };
/// Class represents Http Response Header.
struct THttpHeader: public THttpBaseHeader {
public:
    i8 accept_ranges;
    i8 squid_error;
    i8 x_robots_tag; // deprecated, use x_robots_state instead
    i16 http_status;
    TString location;
    TString rel_canonical;
    char hreflangs[HREFLANG_MAX];
    i64 retry_after;
    TString x_robots_state; // 'xxxxx' format, see `library/html/zoneconf/parsefunc.cpp`

public:
    void Init() {
        THttpBaseHeader::Init();
        accept_ranges = -1;
        squid_error = 0;
        x_robots_tag = 0;
        rel_canonical.clear();
        http_status = -1;
        location.clear();
        hreflangs[0] = 0;
        retry_after = DEFAULT_RETRY_AFTER;
        x_robots_state = "xxxxx";
    }

    void Print() const {
        THttpBaseHeader::Print();
        printf("http_status: %" PRIi16 "\n", http_status);
        printf("squid_error: %" PRIi8 "\n", squid_error);
        printf("accept_ranges: %" PRIi8 "\n", accept_ranges);
        printf("location: \"%s\"\n", location.c_str());
        printf("retry_after: %" PRIi64 "\n", retry_after);
    }
};

struct THttpRequestHeader: public THttpBaseHeader {
public:
    TString request_uri;
    char host[HOST_MAX];
    char from[MAXWORD_LEN];
    char user_agent[MAXWORD_LEN];
    char x_yandex_langregion[MAX_LANGREGION_LEN];
    char x_yandex_sourcename[MAXWORD_LEN];
    char x_yandex_requesttype[MAXWORD_LEN];
    char x_yandex_fetchoptions[MAXWORD_LEN];
    i8 http_method;
    i8 x_yandex_request_priority;
    i32 x_yandex_response_timeout;
    i32 max_age;
    i64 if_modified_since;

public:
    THttpRequestHeader() {
        Init();
    }

    void Init() {
        request_uri.clear();
        host[0] = 0;
        from[0] = 0;
        user_agent[0] = 0;
        x_yandex_langregion[0] = 0;
        x_yandex_sourcename[0] = 0;
        x_yandex_requesttype[0] = 0;
        x_yandex_fetchoptions[0] = 0;
        http_method = HTTP_METHOD_UNDEFINED;
        x_yandex_request_priority = DEFAULT_REQUEST_PRIORITY;
        x_yandex_response_timeout = DEFAULT_RESPONSE_TIMEOUT;
        max_age = DEFAULT_MAX_AGE;
        if_modified_since = DEFAULT_IF_MODIFIED_SINCE;
        THttpBaseHeader::Init();
    }

    void Print() const {
        THttpBaseHeader::Print();
        printf("request_uri: \"%s\"\n", request_uri.c_str());
        printf("host: \"%s\"\n", host);
        printf("from: \"%s\"\n", from);
        printf("user_agent: \"%s\"\n", user_agent);
        printf("http_method: %" PRIi8 "\n", http_method);
        printf("response_timeout: %" PRIi32 "\n", x_yandex_response_timeout);
        printf("max_age: %" PRIi32 "\n", max_age);
        printf("if_modified_since: %" PRIi64 "\n", if_modified_since);
    }

    /// It doesn't care about errors in request or headers, where
    /// request_uri equals to '*'.
    /// This returns copy of the string, which you have to delete.
    TString GetUrl() {
        TString url;
        if (host[0] == 0 || !strcmp(host, "")) {
            url = request_uri;
        } else {
            url = HTTP_PREFIX;
            url += host;
            url += request_uri;
        }
        return url;
    }

    char* GetUrl(char* buffer, size_t size) {
        if (host[0] == 0 || !strcmp(host, "")) {
            strlcpy(buffer, request_uri.c_str(), size);
        } else {
            snprintf(buffer, size, "http://%s%s", host, request_uri.c_str());
        }
        return buffer;
    }
};

class THttpAuthHeader: public THttpHeader {
public:
    char* realm;
    char* nonce;
    char* opaque;
    bool stale;
    int algorithm;
    bool qop_auth;
    bool use_auth;

    //we do not provide auth-int variant as too heavy
    //bool  qop_auth_int;

    THttpAuthHeader()
        : realm(nullptr)
        , nonce(nullptr)
        , opaque(nullptr)
        , stale(false)
        , algorithm(0)
        , qop_auth(false)
        , use_auth(true)
    {
        THttpHeader::Init();
    }

    ~THttpAuthHeader() {
        free(realm);
        free(nonce);
        free(opaque);
    }

    void Print() {
        THttpHeader::Print();
        if (use_auth) {
            if (realm)
                printf("realm: \"%s\"\n", realm);
            if (nonce)
                printf("nonce: \"%s\"\n", nonce);
            if (opaque)
                printf("opaque: \"%s\"\n", opaque);
            printf("stale: %d\n", stale);
            printf("algorithm: %d\n", algorithm);
            printf("qop_auth: %d\n", qop_auth);
        }
    }
};