#pragma once
#include "Json.h"
#include <string>
#include <vector>
#include <queue>
#include <mutex>
#include <thread>
#include <functional>
#include <atomic>
#include <iostream>
#include <unordered_map>
#include <sstream>
#include <cstring>

#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>

// AgentAPI: HTTP JSON server for AI agent control of the engine.
//
// Runs a lightweight HTTP server on a background thread.
// AI agents (OpenClaw, etc.) POST JSON commands to manipulate the world.
//
// Endpoint: POST http://localhost:<port>/api
//
// Command schema:
//
//   Create object:
//     {"action":"create","type":"cube|sphere|cylinder|cone|plane|terrain",
//      "id":"myobj","position":[x,y,z],"scale":[x,y,z],"rotation":[x,y,z],
//      "color":[r,g,b],"segments":16,"radius":1,"height":2,
//      "resolution":64,"size":100,"max_height":8}
//
//   Move/transform object:
//     {"action":"move","id":"myobj","position":[x,y,z]}
//     {"action":"rotate","id":"myobj","rotation":[x,y,z]}
//     {"action":"scale","id":"myobj","scale":[x,y,z]}
//
//   Delete object:
//     {"action":"delete","id":"myobj"}
//
//   Set camera:
//     {"action":"camera","position":[x,y,z],"target":[x,y,z]}
//
//   Camera orbit:
//     {"action":"orbit","distance":20,"angle":45,"height":10}
//
//   Play sound:
//     {"action":"sound","name":"click","volume":1.0}
//     {"action":"music","name":"bgm","volume":0.7}
//     {"action":"stop_music"}
//
//   Query world state:
//     {"action":"query","what":"objects|camera|stats"}
//
//   Set engine property:
//     {"action":"set","property":"fov|draw_distance|clear_color|time_scale",
//      "value":60.0}
//     {"action":"set","property":"clear_color","value":[0.1,0.1,0.2]}
//
//   Batch multiple commands:
//     {"action":"batch","commands":[{...},{...},...]}
//
//   Clear all objects:
//     {"action":"clear"}
//
//   Spawn text in world (billboard):
//     {"action":"log","message":"Hello from AI"}
//
//   Save current world to worlds/<name>.json:
//     {"action":"save_world","name":"desert"}
//
//   Load world from worlds/<name>.json (wipes current):
//     {"action":"load_world","name":"desert"}
//
//   List available worlds:
//     {"action":"list_worlds"}
//

struct AgentCommand {
    json::Value data;
    std::string responseBody;  // filled by processor
    bool processed = false;
    bool hasResponse = false;
};

class AgentAPI {
public:
    using CommandProcessor = std::function<std::string(const json::Value&)>;

    bool start(int port = 9090) {
        port_ = port;
        running_ = true;

        serverFd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (serverFd_ < 0) {
            std::cerr << "AgentAPI: socket() failed" << std::endl;
            return false;
        }

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

        // Non-blocking
        fcntl(serverFd_, F_SETFL, O_NONBLOCK);

        struct sockaddr_in addr{};
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); // localhost only
        addr.sin_port = htons(port);

        if (bind(serverFd_, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
            std::cerr << "AgentAPI: bind() failed on port " << port << std::endl;
            close(serverFd_);
            serverFd_ = -1;
            return false;
        }

        listen(serverFd_, 16);

        thread_ = std::thread(&AgentAPI::serverLoop, this);
        std::cout << "AgentAPI: listening on http://localhost:" << port << "/api" << std::endl;
        return true;
    }

    void stop() {
        running_ = false;
        if (thread_.joinable()) thread_.join();
        if (serverFd_ >= 0) { close(serverFd_); serverFd_ = -1; }
    }

    // Call from main thread each frame — returns queued commands
    std::vector<json::Value> pollCommands() {
        std::lock_guard<std::mutex> lock(mutex_);
        std::vector<json::Value> cmds;
        while (!commandQueue_.empty()) {
            cmds.push_back(std::move(commandQueue_.front()));
            commandQueue_.pop();
        }
        return cmds;
    }

    // Set the response for query commands (called from main thread)
    void setQueryResponse(const std::string& response) {
        std::lock_guard<std::mutex> lock(responseMutex_);
        lastQueryResponse_ = response;
    }

    int getPort() const { return port_; }
    bool isRunning() const { return running_; }

    // Agent message log (for displaying in overlay)
    std::string popLastLog() {
        std::lock_guard<std::mutex> lock(logMutex_);
        std::string msg = lastLog_;
        lastLog_.clear();
        return msg;
    }

private:
    int port_ = 9090;
    int serverFd_ = -1;
    std::atomic<bool> running_{false};
    std::thread thread_;

    std::mutex mutex_;
    std::queue<json::Value> commandQueue_;

    std::mutex responseMutex_;
    std::string lastQueryResponse_ = "{}";

    std::mutex logMutex_;
    std::string lastLog_;

    void serverLoop() {
        while (running_) {
            struct pollfd pfd{};
            pfd.fd = serverFd_;
            pfd.events = POLLIN;

            int ready = poll(&pfd, 1, 100); // 100ms timeout
            if (ready <= 0) continue;

            struct sockaddr_in clientAddr{};
            socklen_t clientLen = sizeof(clientAddr);
            int clientFd = accept(serverFd_, (struct sockaddr*)&clientAddr, &clientLen);
            if (clientFd < 0) continue;

            // Ensure client socket is blocking
            int flags = fcntl(clientFd, F_GETFL, 0);
            fcntl(clientFd, F_SETFL, flags & ~O_NONBLOCK);
            handleClient(clientFd);
            close(clientFd);
        }
    }

    void handleClient(int fd) {
        // Set client socket to blocking with a timeout
        struct timeval tv;
        tv.tv_sec = 2;
        tv.tv_usec = 0;
        setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));

        // Read request (up to 64KB)
        char buf[65536];
        int total = 0;
        while (total < (int)sizeof(buf) - 1) {
            int n = (int)recv(fd, buf + total, sizeof(buf) - 1 - total, 0);
            if (n <= 0) break;
            total += n;
            buf[total] = '\0';
            // Check if we have full headers + body
            char* headerEnd = strstr(buf, "\r\n\r\n");
            if (headerEnd) {
                int headerLen = (int)(headerEnd - buf) + 4;
                // Find Content-Length
                int contentLen = 0;
                char* cl = strcasestr(buf, "Content-Length:");
                if (cl) contentLen = atoi(cl + 15);
                if (total >= headerLen + contentLen) break;
            }
        }

        std::string request(buf, total);

        // Extract body
        auto bodyStart = request.find("\r\n\r\n");
        std::string body;
        if (bodyStart != std::string::npos)
            body = request.substr(bodyStart + 4);

        // CORS headers for browser-based agents
        std::string corsHeaders =
            "Access-Control-Allow-Origin: *\r\n"
            "Access-Control-Allow-Methods: POST, GET, OPTIONS\r\n"
            "Access-Control-Allow-Headers: Content-Type\r\n";

        // Handle OPTIONS (CORS preflight)
        if (request.find("OPTIONS") == 0) {
            std::string resp = "HTTP/1.1 204 No Content\r\n" + corsHeaders + "\r\n";
            send(fd, resp.c_str(), resp.size(), 0);
            return;
        }

        // Handle GET /api/schema — return command schema
        if (request.find("GET /api/schema") != std::string::npos ||
            request.find("GET /schema") != std::string::npos) {
            std::string schema = getSchema();
            sendJson(fd, 200, schema, corsHeaders);
            return;
        }

        // Handle GET /api/status
        if (request.find("GET /api/status") != std::string::npos ||
            request.find("GET /status") != std::string::npos) {
            std::lock_guard<std::mutex> lock(responseMutex_);
            sendJson(fd, 200, lastQueryResponse_, corsHeaders);
            return;
        }

        // Handle POST /api
        if (request.find("POST") == 0) {
            if (body.empty()) {
                sendJson(fd, 400, R"({"ok":false,"error":"empty body"})", corsHeaders);
                return;
            }
            try {
                json::Value cmd = json::parse(body);
                std::string action = cmd.get_string("action", "");

                // Handle batch
                if (action == "batch" && cmd.has("commands") && cmd["commands"].is_array()) {
                    for (auto& c : cmd["commands"].as_array()) {
                        std::lock_guard<std::mutex> lock(mutex_);
                        commandQueue_.push(c);
                    }
                    sendJson(fd, 200, R"({"ok":true,"queued":)" +
                             std::to_string(cmd["commands"].size()) + "}", corsHeaders);
                    return;
                }

                // Handle log
                if (action == "log") {
                    std::lock_guard<std::mutex> lock(logMutex_);
                    lastLog_ = cmd.get_string("message", "");
                }

                // Handle query (return cached state)
                if (action == "query") {
                    std::lock_guard<std::mutex> lock(responseMutex_);
                    // Also queue it so engine updates the response
                    {
                        std::lock_guard<std::mutex> lock2(mutex_);
                        commandQueue_.push(cmd);
                    }
                    sendJson(fd, 200, lastQueryResponse_, corsHeaders);
                    return;
                }

                // Queue command for main thread
                {
                    std::lock_guard<std::mutex> lock(mutex_);
                    commandQueue_.push(cmd);
                }

                sendJson(fd, 200, R"({"ok":true})", corsHeaders);
            } catch (const std::exception& e) {
                std::string err = R"({"ok":false,"error":")" + std::string(e.what()) + "\"}";
                sendJson(fd, 400, err, corsHeaders);
            }
            return;
        }

        // Handle GET /api — return status
        if (request.find("GET /api") != std::string::npos) {
            std::lock_guard<std::mutex> lock(responseMutex_);
            sendJson(fd, 200, lastQueryResponse_, corsHeaders);
            return;
        }

        // Default: 404
        sendJson(fd, 404, R"({"error":"Not found. POST to /api or GET /api/schema"})", corsHeaders);
    }

    void sendJson(int fd, int status, const std::string& body, const std::string& extraHeaders) {
        std::string statusText = (status == 200) ? "OK" : (status == 400) ? "Bad Request" : "Not Found";
        std::string resp =
            "HTTP/1.1 " + std::to_string(status) + " " + statusText + "\r\n"
            "Content-Type: application/json\r\n"
            "Content-Length: " + std::to_string(body.size()) + "\r\n"
            + extraHeaders +
            "Connection: close\r\n"
            "\r\n" + body;
        send(fd, resp.c_str(), resp.size(), 0);
    }

    std::string getSchema() {
        return R"SCHEMA({
  "name": "ZoomEngine Agent API",
  "version": "0.1",
  "endpoint": "POST /api",
  "commands": {
    "create": {
      "description": "Create a game object",
      "fields": {
        "type": "cube|sphere|cylinder|cone|plane|grid|terrain",
        "id": "string (optional, for later reference)",
        "position": "[x, y, z]",
        "scale": "[x, y, z] (default [1,1,1])",
        "rotation": "[rx, ry, rz] degrees",
        "color": "[r, g, b] 0-1 range",
        "segments": "int (sphere/cylinder/cone)",
        "radius": "float (cylinder/cone)",
        "height": "float (cylinder/cone)",
        "width": "float (plane)",
        "resolution": "int (terrain)",
        "size": "float (terrain)",
        "max_height": "float (terrain)"
      }
    },
    "move":   {"description": "Set object position",  "fields": {"id": "string", "position": "[x,y,z]"}},
    "rotate": {"description": "Set object rotation",  "fields": {"id": "string", "rotation": "[x,y,z]"}},
    "scale":  {"description": "Set object scale",     "fields": {"id": "string", "scale": "[x,y,z]"}},
    "delete": {"description": "Remove object by id",  "fields": {"id": "string"}},
    "clear":  {"description": "Remove all objects"},
    "camera": {"description": "Set camera position/target", "fields": {"position": "[x,y,z]", "target": "[x,y,z]"}},
    "orbit":  {"description": "Orbit camera", "fields": {"distance": "float", "angle": "float", "height": "float"}},
    "sound":  {"description": "Play sound effect", "fields": {"name": "string", "volume": "float 0-1"}},
    "music":  {"description": "Play looping music", "fields": {"name": "string", "volume": "float 0-1"}},
    "stop_music": {"description": "Stop music"},
    "set":    {"description": "Set engine property", "fields": {"property": "fov|draw_distance|clear_color|time_scale", "value": "number or [r,g,b]"}},
    "query":  {"description": "Query world state", "fields": {"what": "objects|camera|stats"}},
    "batch":  {"description": "Execute multiple commands", "fields": {"commands": "[{...}, ...]"}},
    "log":    {"description": "Display message in engine", "fields": {"message": "string"}}
  }
})SCHEMA";
    }

#ifdef __linux__
    static char* strcasestr(const char* haystack, const char* needle) {
        // Simple fallback
        size_t nlen = strlen(needle);
        for (; *haystack; ++haystack) {
            if (strncasecmp(haystack, needle, nlen) == 0) return (char*)haystack;
        }
        return nullptr;
    }
#endif
};
