#pragma once

//major minor patch
#define LITTLESNITCH_VERSION 100

#include <iostream>
#include <map>
#include <vector>
#include <iostream>
#include <algorithm>
#include <string>

#include <qdebug.h>

#include <nlohmann/json.hpp>

using json = nlohmann::json;

template<typename T, typename K>
bool json_get(json j, T& value, K key) noexcept {
    try {
        j[key].get_to(value);
        return true;
    } catch (nlohmann::detail::type_error& err) {
        std::cout << "key " << key << " error " << err.what();
    } catch (nlohmann::detail::out_of_range& err) {
        std::cout << "key " << key << " error " << err.what();
    } catch (nlohmann::detail::other_error& err) {
        std::cout << "key " << key << " error " << err.what();
    }
    return false;
}

template<typename T, typename K, typename... Ks>
bool json_get(json j, T& value, K key, Ks... keys) noexcept {
    try {
        return json_get(j[key], value, keys...);
    } catch (nlohmann::detail::type_error& err) {
        std::cout << "key " << key << " error " << err.what();
    } catch (nlohmann::detail::out_of_range& err) {
        std::cout << "key " << key << " error " << err.what();
    } catch (nlohmann::detail::other_error& err) {
        std::cout << "key " << key << " error " << err.what();
    }
    return false;
}


//! represents one item in the http history.
struct HistoryItem {
    int id = -1;
    double timestamp = 0;
    std::string method;
    std::string scheme;
    std::string host;
    unsigned short port = 0;
    std::string path;
    int status_code = 0;
    std::string reason;
    double rtt = 0.0;
    size_t size = 0;
    std::string request_http_version;
    std::map<std::string, std::string> request_headers;
    std::string request_content;
    std::string response_http_version;
    std::map<std::string, std::string> response_headers;
    std::string response_content;
};

namespace http {

struct Request
{
    std::string server_ip_address;

    bool tls;
    std::string content;
    std::string scheme;
    std::string method;
    std::string host;
    unsigned short port;
    std::string http_version;
    std::string path;
    double timestamp_start;
    double timestamp_end;
    std::vector<std::tuple<std::string,std::string>> headers;

    std::string error;

};

struct Response {
    int status_code;
    std::string http_version;
    std::string reason;
    std::string content;
    double timestamp_start;
    double timestamp_end;
    std::vector<std::tuple<std::string,std::string>> headers;
};

struct Flow {
    std::string uid;
    Request request;
    Response response;
};


inline void to_json(json& j, const Flow& flow) {}

inline void from_json(const json& j, Flow& flow) {
    //std::cout << std::setw(4) << j << "\n\n";
    json j_flow;
    if(!json_get(j, j_flow, "flow")) {
        return;
    }

    json_get(j_flow, flow.uid, "id");
    if(json j_request; json_get(j_flow, j_request, "request")) {
        json_get(j_request, flow.request.tls, "server_conn", "tls_established");
        json_get(j_request, flow.request.port, "port");
        json_get(j_request, flow.request.host, "host");
        json_get(j_request, flow.request.scheme, "scheme");
        json_get(j_request, flow.request.path, "path");
        json_get(j_request, flow.request.content, "content");
        json_get(j_request, flow.request.method, "method");
        json_get(j_request, flow.request.http_version, "http_version");
        json_get(j_request, flow.request.timestamp_start, "timestamp_start");
        json_get(j_request, flow.request.timestamp_end, "timestamp_end");

        json j_headers;
        if(json_get(j_request, j_headers, "headers")) {
            for(auto& [k,v] : j_headers.items()) {
                flow.request.headers.push_back(std::make_tuple(v.at(0), v.at(1)));
            }
        }
    }
    if(json j_response; json_get(j_flow, j_response, "response")) {
        json_get(j_response, flow.response.status_code, "status_code");
        json_get(j_response, flow.response.http_version, "http_version");
        json_get(j_response, flow.response.reason, "reason");
        json_get(j_response, flow.response.content, "content");
        json_get(j_response, flow.response.timestamp_start, "timestamp_start");
        json_get(j_response, flow.response.timestamp_end, "timestamp_end");

        json j_headers;
        if(json_get(j_response, j_headers, "headers")) {
            for(auto& [k,v] : j_headers.items()) {
                flow.response.headers.push_back(std::make_tuple(v.at(0), v.at(1)));
            }
        }
    }
}

}

Q_DECLARE_METATYPE(http::Flow)