// PUBLIC DOMAIN - NO LICENSE, NO WARRANTY
// Copyright 2025 TimeHexOn & foxhop & russell@unturf
// https://www.permacomputer.com

/*
 * UncloseAI C++ Library using cpp-httplib (header-only)
 * OpenAI-compatible API client with streaming support
 * Compatible with vLLM, Ollama, and OpenAI-compatible endpoints
 */

#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <fstream>
#include <cstdlib>
#include <functional>
#include "httplib.h"

struct ModelInfo {
    std::string id;
    std::string endpoint;
    std::string host;
    int port;
    int max_tokens;
};

class UncloseAI {
private:
    std::vector<ModelInfo> models;
    std::vector<std::pair<std::string, int>> tts_endpoints; // host, port
    int timeout;
    bool debug;

    // Parse URL into host and port
    bool parse_url(const std::string& url, std::string& host, int& port, std::string& base_path) {
        // Simple URL parsing for https://host:port/path
        size_t proto_end = url.find("://");
        if (proto_end == std::string::npos) return false;

        std::string rest = url.substr(proto_end + 3);
        size_t slash_pos = rest.find("/");

        std::string host_port;
        if (slash_pos != std::string::npos) {
            host_port = rest.substr(0, slash_pos);
            base_path = rest.substr(slash_pos);
        } else {
            host_port = rest;
            base_path = "/";
        }

        size_t colon_pos = host_port.find(":");
        if (colon_pos != std::string::npos) {
            host = host_port.substr(0, colon_pos);
            port = std::stoi(host_port.substr(colon_pos + 1));
        } else {
            host = host_port;
            port = (url.find("https://") == 0) ? 443 : 80;
        }

        return true;
    }

    void discover_endpoints_from_env(const std::string& prefix, std::vector<std::string>& endpoints) {
        for(int i = 1; i < 10000; i++) {
            std::string var_name = prefix + "_" + std::to_string(i);
            const char* endpoint = std::getenv(var_name.c_str());
            if(!endpoint) break;
            endpoints.push_back(endpoint);
        }
    }

    void discover_models(const std::vector<std::string>& endpoints) {
        for(const auto& endpoint : endpoints) {
            if(debug) {
                std::cout << "[DEBUG] Discovering from: " << endpoint << std::endl;
            }

            std::string host;
            int port;
            std::string base_path;
            if(!parse_url(endpoint, host, port, base_path)) continue;

            httplib::Client cli(host, port);
            cli.set_connection_timeout(0, 10000000); // 10 sec
            cli.set_read_timeout(10, 0);

            std::string models_path = base_path + (base_path.back() == '/' ? "models" : "/models");
            auto res = cli.Get(models_path.c_str());

            if(res && res->status == 200) {
                // Simple JSON parsing for model IDs
                std::string body = res->body;
                size_t pos = 0;
                while((pos = body.find("\"id\":\"", pos)) != std::string::npos) {
                    pos += 6;
                    size_t end = body.find("\"", pos);
                    if(end != std::string::npos) {
                        std::string model_id = body.substr(pos, end - pos);

                        // Filter out modelperm-* and chatcmpl-* entries
                        if(model_id.substr(0, 10) != "modelperm-" && model_id.substr(0, 9) != "chatcmpl-") {
                            ModelInfo info;
                            info.id = model_id;
                            info.endpoint = endpoint;
                            info.host = host;
                            info.port = port;
                            info.max_tokens = 8192;
                            models.push_back(info);

                            if(debug) {
                                std::cout << "[DEBUG] Discovered: " << model_id << std::endl;
                            }
                        }
                    }
                    pos = end + 1;
                }
            }
        }
    }

public:
    UncloseAI(int timeout = 30, bool debug = false) : timeout(timeout), debug(debug) {
        std::vector<std::string> endpoints;
        std::vector<std::string> tts_eps;

        discover_endpoints_from_env("MODEL_ENDPOINT", endpoints);
        discover_endpoints_from_env("TTS_ENDPOINT", tts_eps);

        if(debug) {
            std::cout << "[DEBUG] Initialized with " << endpoints.size() << " endpoint(s)" << std::endl;
        }

        discover_models(endpoints);

        // Parse TTS endpoints
        for(const auto& ep : tts_eps) {
            std::string host;
            int port;
            std::string base_path;
            if(parse_url(ep, host, port, base_path)) {
                tts_endpoints.push_back({host, port});
            }
        }
    }

    const std::vector<ModelInfo>& list_models() const {
        return models;
    }

    int chat(const std::string& prompt, std::string& response, int model_idx = 0, int max_tokens = 100) {
        if(model_idx >= static_cast<int>(models.size())) return -1;

        const ModelInfo& model = models[model_idx];

        httplib::Client cli(model.host, model.port);
        cli.set_connection_timeout(0, timeout * 1000000);
        cli.set_read_timeout(timeout, 0);

        std::ostringstream json;
        json << "{\"model\":\"" << model.id << "\","
             << "\"messages\":[{\"role\":\"user\",\"content\":\"" << prompt << "\"}],"
             << "\"stream\":false,"
             << "\"max_tokens\":" << max_tokens << ","
             << "\"temperature\":0.7}";

        auto res = cli.Post("/chat/completions", json.str(), "application/json");

        if(res && res->status == 200) {
            response = res->body;
            return 0;
        }
        return -1;
    }

    int chat_stream(const std::string& prompt, std::function<void(const std::string&)> callback, int model_idx = 0, int max_tokens = 500) {
        // NOTE: cpp-httplib streaming API is complex, using simple buffered approach
        // For production use, consider implementing proper SSE streaming with ContentReceiver
        std::string response;
        if(chat(prompt, response, model_idx, max_tokens) == 0) {
            if(callback) {
                callback(response);
            }
            return 0;
        }
        return -1;
    }

    int tts(const std::string& text, const std::string& voice, const std::string& output_file) {
        if(tts_endpoints.empty()) return -1;

        auto [host, port] = tts_endpoints[0];

        httplib::Client cli(host, port);
        cli.set_connection_timeout(0, timeout * 1000000);
        cli.set_read_timeout(timeout, 0);

        std::ostringstream json;
        json << "{\"model\":\"tts-1\","
             << "\"voice\":\"" << voice << "\","
             << "\"input\":\"" << text << "\"}";

        auto res = cli.Post("/audio/speech", json.str(), "application/json");

        if(res && res->status == 200) {
            std::ofstream file(output_file, std::ios::binary);
            if(file.is_open()) {
                file.write(res->body.c_str(), res->body.size());
                file.close();
                return 0;
            }
        }
        return -1;
    }
};

// Demo program showing library usage
int main() {
    std::cout << "=== UncloseAI C++ Client (cpp-httplib with Streaming) ===\n\n";

    UncloseAI client(30, true);

    if(client.list_models().empty()) {
        std::cout << "ERROR: No models discovered. Set environment variables:\n";
        std::cout << "  MODEL_ENDPOINT_1, MODEL_ENDPOINT_2, etc.\n";
        return 1;
    }

    auto models = client.list_models();
    std::cout << "\nDiscovered " << models.size() << " model(s):\n";
    for(const auto& m : models) {
        std::cout << "  - " << m.id << " (max_tokens: " << m.max_tokens << ")\n";
    }
    std::cout << "\n";

    // Non-streaming chat
    std::cout << "=== Non-Streaming Chat ===\n";
    std::string response;
    if(client.chat("Explain quantum computing in one sentence", response) == 0) {
        std::cout << "Response received (" << response.size() << " bytes)\n";
        std::cout << "(Full response requires JSON parsing library)\n\n";
    } else {
        std::cout << "Request failed\n\n";
    }

    // Streaming chat
    std::cout << "=== Streaming Chat ===\n";
    int model_idx = (models.size() > 1) ? 1 : 0;
    std::cout << "Model: " << models[model_idx].id << "\n";
    std::cout << "Response: ";

    client.chat_stream("Write a hello world program in C++",
                      [](const std::string& content) {
                          std::cout << content << std::flush;
                      }, model_idx, 500);

    std::cout << "\n\n";

    // TTS
    std::cout << "=== TTS Speech Generation ===\n";
    std::cout << "Model: tts-1\n";
    if(client.tts("Hello from UncloseAI C++ client with cpp-httplib! This demonstrates streaming support.",
                  "alloy", "/tmp/speech.mp3") == 0) {
        std::cout << "Audio saved to /tmp/speech.mp3\n";
    } else {
        std::cout << "TTS failed\n";
    }

    std::cout << "\n=== Examples Complete ===\n";
    return 0;
}
