#include <sstream>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <ifaddrs.h>
#include <curl/curl.h>
#include <fstream>
#include "utils.h"

namespace hm {
    Level gConfLogLevel = Warn;
    std::string gConfFaceRemote = "http://127.0.0.1/", gConfCarRemote = "http://127.0.0.1/",  // NOLINT
    gConfLoginUser = "<username here>", gConfLoginPass = "<password here>";  // NOLINT
    unsigned int gConfSearchGap = 60, gConfSearchDuration = 4, gConfServerPort = 8040;

    void ReadConfig(const char *path) {
        std::ifstream file(path);
        if (!file) {
            LogM(Error, "config file \"%s\" open failed.", path);
        } else {
            std::string line, key, value;
            while (std::getline(file, line)) {
                if (line.empty() || line.size() == 1 || line[0] == '#') continue;
                if (splitKeyValue(line, key, value)) {
                    if (key == "faceRemote") gConfFaceRemote = value;
                    else if (key == "carRemote") gConfCarRemote = value;
                    else if (key == "user") gConfLoginUser = value;
                    else if (key == "pass") gConfLoginPass = value;
                    else if (key == "logLevel") gConfLogLevel = (Level) std::stoi(value);
                    else if (key == "searchGap") gConfSearchGap = std::stoi(value);
                    else if (key == "searchDuration") gConfSearchDuration = std::stoi(value);
                    else if (key == "serverPort") gConfServerPort = std::stoi(value);
                    else
                        LogM(Warn, "unrecognized config key[%s], value[%s]", key.c_str(), value.c_str());
                }
            }
        }
        Log(
                Trace, "{faceRemote:%s, carRemote:%s, user:%s, pass:%s, logLevel:%d, port:%d, searchGap:%d, searchDuration:%d}",
                gConfFaceRemote.c_str(), gConfCarRemote.c_str(), gConfLoginUser.c_str(), gConfLoginPass.c_str(),
                gConfLogLevel, gConfServerPort, gConfSearchGap, gConfSearchDuration
        );
    }

    const char *logLevelName(const Level &level) {
        switch (level) {
            case Trace:
                return "TRACE";
            case Info:
                return "INFO";
            case Warn:
                return "WARN";
            case Error:
                return "ERROR";
            default:
                return "NULL";
        }
    }

    bool getLogStatus(const Level &level) {
        return gConfLogLevel && level >= gConfLogLevel;
    }

    bool startsWith(const std::string &str, const std::string &start) {
        if (str.length() < start.length()) {
            return false;
        }
        return str.compare(0, start.length(), start) == 0;
    }

    bool endsWith(const std::string &str, const std::string &end) {
        if (str.length() < end.length()) {
            return false;
        }
        return str.compare(str.length() - end.length(), end.length(), end) == 0;
    }

    std::string trim(const std::string &str) {
        unsigned start = 0, end = str.size() - 1;
        while (std::isspace(str[start])) start++;
        while (std::isspace(str[end])) end--;
        return str.substr(start, end - start + 1);
    }

    bool splitKeyValue(const std::string &src, std::string &key, std::string &value) {
        std::stringstream ss(src);
        std::string token;
        if (std::getline(ss, token, '=') && !token.empty()) {
            key = trim(token);
            if (std::getline(ss, token)) {
                value = trim(token);
                return true;
            }
        }
        return false;
    }

    std::vector<std::string> getIpList() {
        std::vector<std::string> ipList;
        struct ifaddrs *ifap, *ifa;
        if (getifaddrs(&ifap) == -1) {
            return ipList;
        }
        for (ifa = ifap; ifa != nullptr; ifa = ifa->ifa_next) {
            if (ifa->ifa_addr == nullptr) {
                continue;
            }
            if (ifa->ifa_addr->sa_family == AF_INET) {
                auto *addr = (struct sockaddr_in *) ifa->ifa_addr;
                char localIP[INET_ADDRSTRLEN];
                inet_ntop(AF_INET, &(addr->sin_addr), localIP, INET_ADDRSTRLEN);
                ipList.emplace_back(localIP);
            }
        }
        freeifaddrs(ifap);
        return ipList;
    }

    int imageRead(const std::string &path, char *&buffer) {
        std::ifstream img(path, std::ios::binary);
        img.seekg(0, std::ios::end);
        int size = (int) img.tellg();
        img.seekg(0, std::ios::beg);
        buffer = new char[size];
        img.read(buffer, size);
        return size;
    }

    int imgcpy(char *&dest, const std::string &src) {
        int size = (int) src.size();
        dest = new char[size];
        for (int i = 0; i < size; ++i) dest[i] = src[i];
        return size;
    }

    void imageSave(const std::string &path, char *&start, const int &size) {
        if (size > 0 && start != nullptr) {
            std::ofstream out(path, std::ios::binary);
            if (!out.is_open()) return;
            out.write(start, size);
            out.close();
        }
    }

    void imageSave(const std::string &path, unsigned char *&start, const unsigned int &size) {
        if (size > 0 && start != nullptr) {
            std::ofstream out(path, std::ios::binary);
            if (!out.is_open()) return;
            out.write((char *) start, size);
            out.close();
        }
    }

    time_t toTimestamp(
            const unsigned int &year, const unsigned int &mon, const unsigned int &day,
            const unsigned int &hour, const unsigned int &min, const unsigned int &sec
    ) {
        struct tm info = {
                .tm_sec = (int) sec, .tm_min = (int) min, .tm_hour = (int) hour,
                .tm_mday = (int) day, .tm_mon = (int) mon - 1, .tm_year = (int) year - 1900
        };
        return mktime(&info);
    }

    size_t onPostResponse(void *contents, size_t size, size_t nMem, std::string *output) {
        size_t total_size = size * nMem;
        output->append(static_cast<char *>(contents), total_size);
        return total_size;
    }

    // 22(21+1) errors here, never mind, it's ok anyway.
    void dataUpload(
            const std::string &url, const std::map<std::string, std::string> &data,
            unsigned char *&imgBuf, const unsigned int &bufSize
    ) {
        CURL *curl = curl_easy_init();
        if (!curl) {
            Log(Error, "CURL initialization failed.");
            return;
        }
        curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 3);
        curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
        curl_easy_setopt(curl, CURLOPT_POST, 1);
        struct curl_httppost *form = nullptr;
        struct curl_httppost *last = nullptr;
        // json data
        for (const auto &it : data) {
            curl_formadd(
                    &form, &last, CURLFORM_COPYNAME, it.first.c_str(),
                    CURLFORM_COPYCONTENTS, it.second.c_str(), CURLFORM_END
            );
        }
        // image
        curl_formadd(
                &form, &last, CURLFORM_COPYNAME, "image", CURLFORM_BUFFER, "image.jpg",
                CURLFORM_BUFFERPTR, imgBuf, CURLFORM_BUFFERLENGTH, bufSize, CURLFORM_END
        );
        curl_easy_setopt(curl, CURLOPT_HTTPPOST, form);
        // response
        std::string response;
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, onPostResponse);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
        CURLcode res = curl_easy_perform(curl);
        if (res == CURLE_OPERATION_TIMEDOUT) {
            Log(Warn, "Connection Timeout");
        } else if (res == CURLE_OK) {
            long code;
            curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
            if (code == 200) {
                Log(Trace, "upload success with: %s", response.c_str());
            } else {
                Log(Warn, "upload fail with: code[%ld], msg: %s", code, response.c_str());
            }
        } else {
            Log(Error, "HTTP request failed: %s", curl_easy_strerror(res));
        }
        curl_easy_cleanup(curl);
        curl_formfree(form);
    }
}