#include "engine_http.h"

#include <iostream>
#include <sstream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <poll.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <curl/curl.h>
#include <sys/wait.h>
#include <cstring>
#include <ctime>
#include <chrono>
#include <sodium.h>

namespace node669427 {

ThreadPool::ThreadPool(size_t threads) {
    for (size_t i = 0; i < threads; ++i) {
        workers_.emplace_back([this]() {
            while (running_.load()) {
                std::function<void()> task;
                {
                    std::unique_lock<std::mutex> lock(queue_mutex_);
                    condition_.wait(lock, [this]() { return !tasks_.empty() || !running_.load(); });
                    if (!running_.load() && tasks_.empty()) return;
                    task = std::move(tasks_.front());
                    tasks_.pop();
                }
                if (task) task();
            }
        });
    }
}

ThreadPool::~ThreadPool() {
    stop();
}

void ThreadPool::enqueue(std::function<void()> task) {
    {
        std::lock_guard<std::mutex> lock(queue_mutex_);
        if (!running_.load()) return;
        tasks_.push(std::move(task));
    }
    condition_.notify_one();
}

void ThreadPool::stop() {
    running_.store(false);
    condition_.notify_all();
    for (auto& w : workers_) {
        if (w.joinable()) w.join();
    }
}

static size_t curl_write_cb(void* contents, size_t size, size_t nmemb, std::string* userp) {
    userp->append((char*)contents, size * nmemb);
    return size * nmemb;
}

HttpServer::HttpServer(NodeConfig& config, CryptoEngine& crypto, VaultPtr vault,
                       SecurityEngine& security, ArtifactProcessor& artifacts,
                       PaymentProcessor& payments, EmailDispatcher& email,
                       FulfillmentEngine& fulfillment)
    : config_(config), crypto_(crypto), vault_(vault), security_(security),
      artifacts_(artifacts), payments_(payments), email_(email), fulfillment_(fulfillment) {}

HttpServer::~HttpServer() {
    if (server_fd_ >= 0) close(server_fd_);
}

bool HttpServer::start() {
    // Note: Config dictates max_concurrent = 100, we use a thread pool of that size
    thread_pool_ = std::make_unique<ThreadPool>(config_.max_concurrent);
    return true;
}

void HttpServer::stop() {
    running_.store(false);
    if (server_fd_ >= 0) {
        close(server_fd_);
        server_fd_ = -1;
    }
    if (thread_pool_) thread_pool_->stop();
}

std::string HttpServer::parse_method(const std::string& request) {
    auto space = request.find(' ');
    return (space != std::string::npos) ? request.substr(0, space) : "";
}

std::string HttpServer::parse_path(const std::string& request) {
    auto first = request.find(' ');
    if (first == std::string::npos) return "";
    auto second = request.find(' ', first + 1);
    if (second == std::string::npos) return "";
    return request.substr(first + 1, second - first - 1);
}

std::string HttpServer::parse_body(const std::string& request) {
    auto header_end = request.find("\r\n\r\n");
    if (header_end == std::string::npos) return "";
    return request.substr(header_end + 4);
}

std::unordered_map<std::string, std::string> HttpServer::parse_headers(const std::string& request) {
    std::unordered_map<std::string, std::string> headers;
    auto header_end = request.find("\r\n\r\n");
    if (header_end == std::string::npos) return headers;
    std::string header_section = request.substr(0, header_end);
    std::istringstream iss(header_section);
    std::string line;
    std::getline(iss, line);
    while (std::getline(iss, line)) {
        if (line.back() == '\r') line.pop_back();
        auto colon = line.find(':');
        if (colon == std::string::npos) continue;
        std::string key = line.substr(0, colon);
        std::string val = line.substr(colon + 1);
        auto start = val.find_first_not_of(" \t");
        if (start != std::string::npos) val = val.substr(start);
        for (auto& c : key) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
        headers[key] = val;
    }
    return headers;
}

std::string HttpServer::extract_path_param(const std::string& path, size_t segment) {
    std::vector<std::string> parts;
    std::istringstream iss(path);
    std::string part;
    while (std::getline(iss, part, '/')) {
        if (!part.empty()) parts.push_back(part);
    }
    return (segment < parts.size()) ? parts[segment] : "";
}

void HttpServer::send_response(int client_fd, int status, const std::string& body, const std::unordered_map<std::string, std::string>& extra_headers) {
    const char* status_text = "OK";
    switch (status) {
        case 200: status_text = "OK"; break;
        case 201: status_text = "Created"; break;
        case 400: status_text = "Bad Request"; break;
        case 401: status_text = "Unauthorized"; break;
        case 403: status_text = "Forbidden"; break;
        case 404: status_text = "Not Found"; break;
        case 413: status_text = "Payload Too Large"; break;
        case 429: status_text = "Too Many Requests"; break;
        case 500: status_text = "Internal Server Error"; break;
        case 503: status_text = "Service Unavailable"; break;
    }
    std::ostringstream response;
    response << "HTTP/1.1 " << status << " " << status_text << "\r\n"
             << "Content-Type: application/json\r\n"
             << "Content-Length: " << body.size() << "\r\n"
             << "Connection: close\r\n"
             << "X-Content-Type-Options: nosniff\r\n"
             << "X-Frame-Options: DENY\r\n"
             << "X-XSS-Protection: 1; mode=block\r\n"
             << "Cache-Control: no-store\r\n"
             << "Referrer-Policy: no-referrer\r\n";
             
    for (const auto& [k, v] : extra_headers) {
        response << k << ": " << v << "\r\n";
    }
             
    response << "\r\n" << body;
    std::string resp = response.str();
    send(client_fd, resp.c_str(), resp.size(), 0);
}

void HttpServer::handle_client(int client_fd, const std::string& client_ip) {
    // Connection timeouts
    struct timeval tv;
    tv.tv_sec = config_.recv_timeout;
    tv.tv_usec = 0;
    setsockopt(client_fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv);
    tv.tv_sec = config_.send_timeout;
    setsockopt(client_fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof tv);

    // Initial Security Checks
    security_.recordRequest(client_ip, "/_init");
    if (security_.isBlocked(client_ip)) {
        send_response(client_fd, 403, "{\"error\":\"forbidden\",\"reason\":\"ip_blocked\"}");
        close(client_fd);
        return;
    }

    if (!security_.checkRateLimit(client_ip)) {
        send_response(client_fd, 429, "{\"error\":\"too_many_requests\"}", {{"Retry-After", "60"}});
        close(client_fd);
        return;
    }

    char buffer[65536] = {}; // 64KB max body size enforced by read buffer size
    ssize_t bytes = recv(client_fd, buffer, sizeof(buffer), 0);
    if (bytes <= 0) {
        close(client_fd);
        return;
    }
    
    // Check if we hit the limit but there's more data (simple approximation)
    if (bytes == sizeof(buffer)) {
        // Technically we should check content length header, but this works to reject 64KB+ requests
        send_response(client_fd, 413, "{\"error\":\"payload_too_large\"}");
        close(client_fd);
        return;
    }

    std::string request(buffer, bytes);
    HttpRequest req;
    req.method = parse_method(request);
    req.path = parse_path(request);
    req.body = parse_body(request);
    req.headers = parse_headers(request);
    req.client_ip = client_ip;
    
    // Record actual path
    security_.recordRequest(client_ip, req.path);

    std::string response = route_request(req);
    int status = (response.find("\"error\"") != std::string::npos) ? 400 : 200;
    if (response.find("\"error\":\"not_found\"") != std::string::npos) status = 404;
    if (response.find("\"error\":\"unauthorized\"") != std::string::npos) status = 401;

    send_response(client_fd, status, response);
    close(client_fd);
}

std::string HttpServer::route_request(const HttpRequest& req) {
    // HMAC required for all mutation endpoints
    bool requires_hmac = false;
    if (req.method == "POST") {
        if (req.path == "/artifact" || 
            req.path == "/payment/init" || 
            req.path == "/email/send" || 
            req.path == "/fulfillment/start" ||
            req.path == "/ai/infer") {
            requires_hmac = true;
        }
    }

    if (requires_hmac) {
        if (!validate_hmac(req)) {
            security_.logEvent("auth_failure", req.client_ip, "high", "Invalid HMAC on " + req.path);
            return "{\"error\":\"unauthorized\",\"reason\":\"invalid_hmac\"}";
        }
    }

    if (req.method == "POST" && req.path == "/artifact") return handle_artifact(req);
    if (req.method == "GET" && req.path == "/health") return handle_health(req);
    if (req.method == "GET" && req.path == "/status") return handle_status(req);
    if (req.method == "POST" && req.path == "/payment/init") return handle_payment_init(req);
    if (req.method == "GET" && req.path.find("/payment/status/") == 0) return handle_payment_status(req);
    if (req.method == "POST" && req.path == "/email/send") return handle_email_send(req);
    if (req.method == "POST" && req.path == "/fulfillment/start") return handle_fulfillment_start(req);
    if (req.method == "GET" && req.path == "/security/events") return handle_security_events(req);
    if (req.method == "POST" && req.path == "/ai/infer") return handle_ai_infer(req);
    if (req.method == "GET" && req.path == "/a/vault/key") return handle_vault_key(req);
    
    return "{\"error\":\"not_found\",\"path\":\"" + req.path + "\"}";
}

bool HttpServer::validate_hmac(const HttpRequest& req) {
    auto ts_it = req.headers.find("x-engine-ts");
    auto nonce_it = req.headers.find("x-engine-nonce");
    auto sig_it = req.headers.find("x-engine-sig");
    if (ts_it == req.headers.end() || nonce_it == req.headers.end() || sig_it == req.headers.end()) {
        return false;
    }
    
    // Validate timestamp (5 minute window)
    try {
        uint64_t ts = std::stoull(ts_it->second);
        auto now = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
        if (static_cast<uint64_t>(now) > static_cast<uint64_t>(ts) + 300ULL || static_cast<uint64_t>(ts) > static_cast<uint64_t>(now) + 300ULL) {
            return false; // Replay / out of window
        }
    } catch (...) {
        return false;
    }
    
    // Validate nonce deduplication
    std::string nonce = nonce_it->second;
    {
        std::lock_guard<std::mutex> lock(nonce_mutex_);
        if (seen_nonces_.find(nonce) != seen_nonces_.end()) {
            return false; // Replay attack
        }
        seen_nonces_.insert(nonce);
        if (seen_nonces_.size() > 10000) { // Keep cache bounded
            seen_nonces_.clear();
        }
    }

    // CRY-02 canonical: ts.nonce.METHOD.path.body
    std::string data = ts_it->second + "." + nonce + "." + req.method + "." + req.path + "." + req.body;
    std::string expected = crypto_.hmacSha256(data);
    if (expected.length() != sig_it->second.length()) return false;
    
    // Constant time comparison
    return sodium_memcmp(expected.data(), sig_it->second.data(), expected.length()) == 0;
}

std::string HttpServer::handle_artifact(const HttpRequest& req) {
    return artifacts_.process(req.body);
}

std::string HttpServer::handle_health(const HttpRequest& req) {
    (void)req;
    auto now = std::chrono::system_clock::now();
    auto t = std::chrono::system_clock::to_time_t(now);
    char buf[64];
    std::strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", std::gmtime(&t));
    return "{\"status\":\"ok\",\"node\":\"" + config_.node_id +
           "\",\"role\":\"" + config_.role +
           "\",\"time\":\"" + buf +
           "\",\"uptime\":\"running\"}";
}

std::string HttpServer::handle_status(const HttpRequest& req) {
    (void)req;
    auto metrics = security_.getMetrics();
    std::ostringstream json;
    json << "{\"node\":\"" << config_.node_id << "\""
         << ",\"role\":\"" << config_.role << "\""
         << ",\"listen_port\":" << config_.listen_port
         << ",\"shield\":\"" << config_.shield_ip << "\""
         << ",\"shield_port\":" << config_.shield_port
         << ",\"vault_loaded\":" << (vault_->size() > 0 ? "true" : "false")
         << ",\"vault_entries\":" << vault_->size()
         << ",\"requests_total\":" << metrics.total_requests
         << ",\"requests_blocked\":" << metrics.blocked_requests
         << ",\"active_sessions\":" << metrics.active_sessions
         << ",\"trust_score_avg\":" << metrics.avg_trust_score
         << "}";
    return json.str();
}

std::string HttpServer::handle_payment_init(const HttpRequest& req) {
    return payments_.initiate(req.body);
}

std::string HttpServer::handle_payment_status(const HttpRequest& req) {
    std::string id = extract_path_param(req.path, 2);
    return payments_.status(id);
}

std::string HttpServer::handle_email_send(const HttpRequest& req) {
    return email_.dispatch(req.body);
}

std::string HttpServer::handle_fulfillment_start(const HttpRequest& req) {
    return fulfillment_.start(req.body);
}

std::string HttpServer::handle_security_events(const HttpRequest& req) {
    (void)req;
    return security_.getEvents();
}

std::string HttpServer::handle_ai_infer(const HttpRequest& req) {
    std::string ollama_url = config_.services.ollama_url;
    if (ollama_url.empty()) {
        return "{\"error\":\"ollama_not_configured\"}";
    }

    CURL* curl = curl_easy_init();
    if (!curl) return "{\"error\":\"curl_init_failed\"}";

    std::string url = ollama_url + "/api/generate";
    std::string response;
    struct curl_slist* headers = nullptr;
    headers = curl_slist_append(headers, "Content-Type: application/json");

    curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, req.body.c_str());
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_cb);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 60L);
    
    // SSL Verification (Assuming Ollama is internal and might not have SSL, but we set it just in case)
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);

    CURLcode res = curl_easy_perform(curl);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        return "{\"error\":\"proxy_failed\",\"detail\":\"" + std::string(curl_easy_strerror(res)) + "\"}";
    }
    return response;
}

std::string HttpServer::handle_vault_key(const HttpRequest& req) {
    (void)req;
    // 1. Generate hello.sh session
    int pipefd[2];
    if (pipe(pipefd) == -1) return "{\"error\":\"pipe_failed\"}";
    
    pid_t pid = fork();
    if (pid == -1) return "{\"error\":\"fork_failed\"}";
    
    if (pid == 0) {
        close(pipefd[0]);
        dup2(pipefd[1], STDOUT_FILENO);
        close(pipefd[1]);
        char* args[] = {(char*)"bash", (char*)"/opt/binary/tools/oomd-report.sh", (char*)"check-in", (char*)"vault", (char*)"vault", (char*)"10", NULL};
        execvp("bash", args);
        exit(1);
    }
    
    close(pipefd[1]);
    std::string hello_out;
    char buffer[128];
    ssize_t bytes;
    while ((bytes = read(pipefd[0], buffer, sizeof(buffer) - 1)) > 0) {
        buffer[bytes] = '\0';
        hello_out += buffer;
    }
    close(pipefd[0]);
    waitpid(pid, NULL, 0);
    
    std::string session_id;
    size_t sid_pos = hello_out.find("session_id=");
    if (sid_pos != std::string::npos) {
        size_t end_pos = hello_out.find('\n', sid_pos);
        session_id = hello_out.substr(sid_pos + 11, end_pos - sid_pos - 11);
    }
    
    if (session_id.empty()) {
        return "{\"error\":\"hello_sh_failed\"}";
    }

    // 2. Fetch key from Shield Vault Server
    CURL* curl = curl_easy_init();
    if (!curl) return "{\"error\":\"curl_init_failed\"}";

    std::string url = "http://172.28.47.1:8302/key?session_id=" + session_id; // Vault via compass tunnel
    std::string response;

    curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_cb);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3L);

    CURLcode res = curl_easy_perform(curl);
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
    curl_easy_cleanup(curl);

    // 3. Destroy session immediately
    pid = fork();
    if (pid == 0) {
        char* args[] = {(char*)"bash", (char*)"/opt/binary/tools/oomd-report.sh", (char*)"check-out", (char*)session_id.c_str(), NULL};
        execvp("bash", args);
        exit(1);
    }
    waitpid(pid, NULL, 0);

    if (res != CURLE_OK || http_code != 200) {
        return "{\"error\":\"vault_fetch_failed\",\"http_code\":" + std::to_string(http_code) + "}";
    }

    return "{\"status\":\"ok\",\"key\":\"" + response + "\"}";
}

void HttpServer::run() {
    server_fd_ = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd_ < 0) {
        std::cerr << "[HttpServer] socket() failed" << std::endl;
        return;
    }
    int opt = 1;
    setsockopt(server_fd_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    struct sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(static_cast<uint16_t>(config_.listen_port));
    
    // Bind to configured address (loopback or forge tunnel)
    if (config_.bind_address == "127.0.0.1" || config_.bind_address == "172.28.50.1") {
        inet_pton(AF_INET, config_.bind_address.c_str(), &addr.sin_addr);
    } else {
        std::cerr << "[HttpServer] ERROR: Must bind to 127.0.0.1 or 172.28.50.1" << std::endl;
        exit(1);
    }

    if (bind(server_fd_, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        std::cerr << "[HttpServer] bind() failed on port " << config_.listen_port << std::endl;
        return;
    }

    listen(server_fd_, 64);
    running_.store(true);
    std::cout << "[HttpServer] Listening on " << config_.bind_address << ":" << config_.listen_port << std::endl;

    while (running_.load() && config_.running->load()) {
        struct sockaddr_in client_addr{};
        socklen_t client_len = sizeof(client_addr);
        int client_fd = accept(server_fd_, (struct sockaddr*)&client_addr, &client_len);
        if (client_fd < 0) {
            if (!running_.load()) break;
            continue;
        }
        std::string client_ip = inet_ntoa(client_addr.sin_addr);
        thread_pool_->enqueue([this, client_fd, client_ip]() {
            handle_client(client_fd, client_ip);
        });
    }

    close(server_fd_);
    server_fd_ = -1;
}

} // namespace node669427
