#include <arpa/inet.h>
#include <csignal>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <iomanip>
#include <iostream>
#include <mutex>
#include <netinet/in.h>
#include <sstream>
#include <stdexcept>
#include <string>
#include <sys/socket.h>
#include <unistd.h>
#include <vector>

#include "baidu_face_api.h"
#include "opencv2/opencv.hpp"

namespace {

volatile std::sig_atomic_t g_running = 1;

void handle_signal(int) {
    g_running = 0;
}

std::string now_iso() {
    std::time_t t = std::time(nullptr);
    char buf[32] = {0};
    std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", std::localtime(&t));
    return std::string(buf);
}

std::string json_escape(const std::string& s) {
    std::ostringstream out;
    for (char c : s) {
        switch (c) {
            case '"': out << "\\\""; break;
            case '\\': out << "\\\\"; break;
            case '\b': out << "\\b"; break;
            case '\f': out << "\\f"; break;
            case '\n': out << "\\n"; break;
            case '\r': out << "\\r"; break;
            case '\t': out << "\\t"; break;
            default:
                if (static_cast<unsigned char>(c) < 0x20) {
                    out << "\\u" << std::hex << std::setw(4) << std::setfill('0') << (int)c;
                } else {
                    out << c;
                }
        }
    }
    return out.str();
}

std::string json_string(const std::string& s) {
    return "\"" + json_escape(s) + "\"";
}

int env_int(const char* name, int fallback) {
    const char* value = std::getenv(name);
    if (!value || !*value) return fallback;
    return std::atoi(value);
}

std::string env_str(const char* name, const std::string& fallback) {
    const char* value = std::getenv(name);
    if (!value || !*value) return fallback;
    return std::string(value);
}

std::string get_json_string(const std::string& body, const std::string& key) {
    std::string needle = "\"" + key + "\"";
    size_t pos = body.find(needle);
    if (pos == std::string::npos) return "";
    pos = body.find(':', pos + needle.size());
    if (pos == std::string::npos) return "";
    pos = body.find('"', pos);
    if (pos == std::string::npos) return "";
    ++pos;
    std::string out;
    bool escape = false;
    for (; pos < body.size(); ++pos) {
        char c = body[pos];
        if (escape) {
            switch (c) {
                case 'n': out.push_back('\n'); break;
                case 'r': out.push_back('\r'); break;
                case 't': out.push_back('\t'); break;
                case '"': out.push_back('"'); break;
                case '\\': out.push_back('\\'); break;
                case '/': out.push_back('/'); break;
                default: out.push_back(c); break;
            }
            escape = false;
        } else if (c == '\\') {
            escape = true;
        } else if (c == '"') {
            break;
        } else {
            out.push_back(c);
        }
    }
    return out;
}

static const std::string kBase64Chars =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    "abcdefghijklmnopqrstuvwxyz"
    "0123456789+/";

std::vector<unsigned char> base64_decode(std::string input) {
    const std::string marker = "base64,";
    size_t marker_pos = input.find(marker);
    if (marker_pos != std::string::npos) {
        input = input.substr(marker_pos + marker.size());
    }

    std::vector<int> table(256, -1);
    for (int i = 0; i < 64; i++) {
        table[static_cast<unsigned char>(kBase64Chars[i])] = i;
    }

    std::vector<unsigned char> out;
    int val = 0;
    int valb = -8;
    for (unsigned char c : input) {
        if (std::isspace(c) || c == '=') continue;
        if (table[c] == -1) continue;
        val = (val << 6) + table[c];
        valb += 6;
        if (valb >= 0) {
            out.push_back(static_cast<unsigned char>((val >> valb) & 0xFF));
            valb -= 8;
        }
    }
    return out;
}

std::string base64_encode(const unsigned char* data, size_t len) {
    std::string out;
    int val = 0;
    int valb = -6;
    for (size_t i = 0; i < len; ++i) {
        val = (val << 8) + data[i];
        valb += 8;
        while (valb >= 0) {
            out.push_back(kBase64Chars[(val >> valb) & 0x3F]);
            valb -= 6;
        }
    }
    if (valb > -6) out.push_back(kBase64Chars[((val << 8) >> (valb + 8)) & 0x3F]);
    while (out.size() % 4) out.push_back('=');
    return out;
}

cv::Mat image_from_request(const std::string& body) {
    std::string image = get_json_string(body, "image");
    if (image.empty()) {
        throw std::runtime_error("image is required");
    }
    std::vector<unsigned char> bytes = base64_decode(image);
    if (bytes.empty()) {
        throw std::runtime_error("image base64 decode failed");
    }
    cv::Mat buf(bytes, true);
    cv::Mat mat = cv::imdecode(buf, cv::IMREAD_COLOR);
    if (mat.empty()) {
        throw std::runtime_error("image decode failed");
    }
    return mat;
}

std::string boxes_json(const std::vector<FaceBox>& boxes) {
    std::ostringstream out;
    out << "[";
    for (size_t i = 0; i < boxes.size(); ++i) {
        const FaceBox& b = boxes[i];
        if (i) out << ",";
        out << "{"
            << "\"index\":" << b.index << ","
            << "\"score\":" << b.score << ","
            << "\"center_x\":" << b.center_x << ","
            << "\"center_y\":" << b.center_y << ","
            << "\"width\":" << b.width << ","
            << "\"height\":" << b.height
            << "}";
    }
    out << "]";
    return out.str();
}

std::string feature_json(const Feature& feature) {
    std::ostringstream out;
    out << "{";
    out << "\"size\":" << feature.size << ",";
    out << "\"data\":[";
    for (int i = 0; i < feature.size; ++i) {
        if (i) out << ",";
        out << feature.data[i];
    }
    out << "],";
    out << "\"base64\":" << json_string(base64_encode(
        reinterpret_cast<const unsigned char*>(feature.data),
        sizeof(float) * static_cast<size_t>(feature.size)));
    out << "}";
    return out.str();
}

class FaceService {
public:
    FaceService() {
        sdk_dir_ = env_str("BAIDU_FACE_SDK_DIR",
            "/workspace/city-subcenter/baidu-face-offline-sdk/extracted/Baidu_Face_Offline_SDK_Linux_ARM_8.0/face_offline_sdk");
        api_.get_device_id(device_id_);
        api_.sdk_version(version_);
        int init = api_.sdk_init(sdk_dir_.c_str());
        init_code_ = init;
        auth_ = api_.is_auth();
        if (init != 0) {
            std::cerr << "sdk_init failed: " << init << std::endl;
        }
        api_.load_db_face();
    }

    std::string health() {
        std::lock_guard<std::mutex> lock(mu_);
        std::ostringstream out;
        out << "{\"error_code\":0,\"error_msg\":\"SUCCESS\",\"result\":{"
            << "\"service\":\"baidu-face-local-service\","
            << "\"time\":" << json_string(now_iso()) << ","
            << "\"device_id\":" << json_string(device_id_) << ","
            << "\"sdk_version\":" << json_string(version_) << ","
            << "\"sdk_dir\":" << json_string(sdk_dir_) << ","
            << "\"sdk_init_code\":" << init_code_ << ","
            << "\"is_auth\":" << (auth_ ? "true" : "false") << ","
            << "\"db_face_count\":" << api_.db_face_count()
            << "}}";
        return out.str();
    }

    std::string detect(const std::string& body) {
        cv::Mat mat = image_from_request(body);
        std::lock_guard<std::mutex> lock(mu_);
        std::vector<FaceBox> boxes;
        int face_num = api_.detect(boxes, &mat, 0);
        std::ostringstream out;
        out << "{\"error_code\":0,\"error_msg\":\"SUCCESS\",\"result\":{"
            << "\"face_num\":" << face_num << ","
            << "\"face_list\":" << boxes_json(boxes)
            << "}}";
        return out.str();
    }

    std::string feature(const std::string& body) {
        cv::Mat mat = image_from_request(body);
        std::lock_guard<std::mutex> lock(mu_);
        std::vector<Feature> features;
        std::vector<FaceBox> boxes;
        int face_num = api_.face_feature(features, boxes, &mat);
        std::ostringstream out;
        out << "{\"error_code\":0,\"error_msg\":\"SUCCESS\",\"result\":{"
            << "\"face_num\":" << face_num << ","
            << "\"face_list\":" << boxes_json(boxes) << ","
            << "\"features\":[";
        for (size_t i = 0; i < features.size(); ++i) {
            if (i) out << ",";
            out << feature_json(features[i]);
        }
        out << "]}}";
        return out.str();
    }

    std::string register_user(const std::string& body, bool update) {
        cv::Mat mat = image_from_request(body);
        std::string group_id = get_json_string(body, "group_id");
        std::string user_id = get_json_string(body, "user_id");
        std::string user_info = get_json_string(body, "user_info");
        if (group_id.empty()) throw std::runtime_error("group_id is required");
        if (user_id.empty()) throw std::runtime_error("user_id is required");
        std::lock_guard<std::mutex> lock(mu_);
        std::string sdk_res;
        if (update) {
            api_.user_update(sdk_res, &mat, user_id.c_str(), group_id.c_str(), user_info.c_str());
        } else {
            api_.user_add(sdk_res, &mat, user_id.c_str(), group_id.c_str(), user_info.c_str());
        }
        api_.load_db_face();
        std::vector<Feature> features;
        std::vector<FaceBox> boxes;
        int face_num = api_.face_feature(features, boxes, &mat);
        std::string token_seed = group_id + ":" + user_id;
        std::ostringstream out;
        out << "{\"error_code\":0,\"error_msg\":\"SUCCESS\",\"result\":{"
            << "\"group_id\":" << json_string(group_id) << ","
            << "\"user_id\":" << json_string(user_id) << ","
            << "\"face_token\":" << json_string("local-" + token_seed) << ","
            << "\"sdk_response\":" << json_string(sdk_res) << ","
            << "\"face_num\":" << face_num << ","
            << "\"features\":[";
        for (size_t i = 0; i < features.size(); ++i) {
            if (i) out << ",";
            out << feature_json(features[i]);
        }
        out << "]}}";
        return out.str();
    }

    std::string search(const std::string& body) {
        cv::Mat mat = image_from_request(body);
        std::string group_id_list = get_json_string(body, "group_id_list");
        if (group_id_list.empty()) group_id_list = get_json_string(body, "group_id");
        std::string user_id = get_json_string(body, "user_id");
        if (group_id_list.empty()) throw std::runtime_error("group_id_list is required");
        std::lock_guard<std::mutex> lock(mu_);
        std::string sdk_res;
        api_.identify(sdk_res, &mat, group_id_list.c_str(), user_id.c_str());
        std::ostringstream out;
        out << "{\"error_code\":0,\"error_msg\":\"SUCCESS\",\"result\":{"
            << "\"sdk_response\":" << json_string(sdk_res)
            << "}}";
        return out.str();
    }

    std::string delete_user(const std::string& body) {
        std::string group_id = get_json_string(body, "group_id");
        std::string user_id = get_json_string(body, "user_id");
        if (group_id.empty()) throw std::runtime_error("group_id is required");
        if (user_id.empty()) throw std::runtime_error("user_id is required");
        std::lock_guard<std::mutex> lock(mu_);
        std::string sdk_res;
        api_.user_delete(sdk_res, user_id.c_str(), group_id.c_str());
        api_.load_db_face();
        return "{\"error_code\":0,\"error_msg\":\"SUCCESS\",\"result\":{\"sdk_response\":" + json_string(sdk_res) + "}}";
    }

    std::string group_op(const std::string& body, bool add) {
        std::string group_id = get_json_string(body, "group_id");
        if (group_id.empty()) throw std::runtime_error("group_id is required");
        std::lock_guard<std::mutex> lock(mu_);
        std::string sdk_res;
        if (add) {
            api_.group_add(sdk_res, group_id.c_str());
        } else {
            api_.group_delete(sdk_res, group_id.c_str());
        }
        api_.load_db_face();
        return "{\"error_code\":0,\"error_msg\":\"SUCCESS\",\"result\":{\"sdk_response\":" + json_string(sdk_res) + "}}";
    }

private:
    BaiduFaceApi api_;
    std::mutex mu_;
    std::string device_id_;
    std::string version_;
    std::string sdk_dir_;
    int init_code_ = -1;
    bool auth_ = false;
};

struct Request {
    std::string method;
    std::string path;
    std::string body;
};

bool read_request(int fd, Request& req) {
    std::string data;
    char buf[4096];
    while (data.find("\r\n\r\n") == std::string::npos) {
        ssize_t n = recv(fd, buf, sizeof(buf), 0);
        if (n <= 0) return false;
        data.append(buf, n);
        if (data.size() > 1024 * 1024 * 20) return false;
    }

    size_t header_end = data.find("\r\n\r\n");
    std::string header = data.substr(0, header_end);
    std::istringstream hs(header);
    hs >> req.method >> req.path;

    size_t content_length = 0;
    std::string line;
    while (std::getline(hs, line)) {
        if (!line.empty() && line.back() == '\r') line.pop_back();
        std::string lower = line;
        for (char& c : lower) c = static_cast<char>(std::tolower(c));
        const std::string prefix = "content-length:";
        if (lower.compare(0, prefix.size(), prefix) == 0) {
            content_length = static_cast<size_t>(std::stoul(line.substr(prefix.size())));
        }
    }

    req.body = data.substr(header_end + 4);
    while (req.body.size() < content_length) {
        ssize_t n = recv(fd, buf, sizeof(buf), 0);
        if (n <= 0) return false;
        req.body.append(buf, n);
    }
    if (req.body.size() > content_length) req.body.resize(content_length);
    return true;
}

void send_response(int fd, int status, const std::string& body) {
    std::string status_text = status == 200 ? "OK" : (status == 404 ? "Not Found" : "Bad Request");
    std::ostringstream resp;
    resp << "HTTP/1.1 " << status << " " << status_text << "\r\n"
         << "Content-Type: application/json; charset=utf-8\r\n"
         << "Content-Length: " << body.size() << "\r\n"
         << "Connection: close\r\n\r\n"
         << body;
    std::string s = resp.str();
    send(fd, s.data(), s.size(), 0);
}

std::string error_json(int code, const std::string& msg) {
    return "{\"error_code\":" + std::to_string(code) + ",\"error_msg\":" + json_string(msg) + ",\"result\":null}";
}

}  // namespace

int main() {
    std::signal(SIGTERM, handle_signal);
    std::signal(SIGINT, handle_signal);

    const std::string host = env_str("BAIDU_FACE_SERVICE_HOST", "127.0.0.1");
    const int port = env_int("BAIDU_FACE_SERVICE_PORT", 18080);

    FaceService service;

    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) {
        perror("socket");
        return 1;
    }

    int opt = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    sockaddr_in addr;
    std::memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    if (inet_pton(AF_INET, host.c_str(), &addr.sin_addr) != 1) {
        std::cerr << "Invalid host: " << host << std::endl;
        return 1;
    }

    if (bind(server_fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
        perror("bind");
        return 1;
    }

    if (listen(server_fd, 64) < 0) {
        perror("listen");
        return 1;
    }

    std::cout << "baidu-face-local-service listening on " << host << ":" << port << std::endl;

    while (g_running) {
        sockaddr_in client_addr;
        socklen_t client_len = sizeof(client_addr);
        int client_fd = accept(server_fd, reinterpret_cast<sockaddr*>(&client_addr), &client_len);
        if (client_fd < 0) {
            if (errno == EINTR) continue;
            perror("accept");
            break;
        }

        try {
            Request req;
            if (!read_request(client_fd, req)) {
                send_response(client_fd, 400, error_json(400, "invalid request"));
                close(client_fd);
                continue;
            }

            std::string body;
            int status = 200;
            if (req.method == "GET" && req.path == "/health") {
                body = service.health();
            } else if (req.method == "POST" && req.path == "/face/detect") {
                body = service.detect(req.body);
            } else if (req.method == "POST" && req.path == "/face/feature") {
                body = service.feature(req.body);
            } else if (req.method == "POST" && req.path == "/face/register") {
                body = service.register_user(req.body, false);
            } else if (req.method == "POST" && req.path == "/face/update") {
                body = service.register_user(req.body, true);
            } else if (req.method == "POST" && req.path == "/face/search") {
                body = service.search(req.body);
            } else if (req.method == "POST" && req.path == "/face/delete-user") {
                body = service.delete_user(req.body);
            } else if (req.method == "POST" && req.path == "/face/group-add") {
                body = service.group_op(req.body, true);
            } else if (req.method == "POST" && req.path == "/face/group-delete") {
                body = service.group_op(req.body, false);
            } else {
                status = 404;
                body = error_json(404, "not found");
            }
            send_response(client_fd, status, body);
        } catch (const std::exception& e) {
            send_response(client_fd, 400, error_json(400, e.what()));
        }
        close(client_fd);
    }

    close(server_fd);
    return 0;
}
