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

/*
 * UncloseAI C Client using libsoup (GNOME HTTP Library)
 * OpenAI-compatible API client with streaming support
 * Compatible with vLLM, Ollama, and OpenAI-compatible endpoints
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libsoup/soup.h>

#define MAX_ENDPOINTS 100
#define MAX_MODELS 100
#define MAX_URL_LEN 512
#define MAX_MODEL_LEN 256

// Structure to hold discovered model info
struct ModelInfo {
    char id[MAX_MODEL_LEN];
    char endpoint[MAX_URL_LEN];
    int max_tokens;
};

struct ModelInfo models[MAX_MODELS];
int model_count = 0;

char tts_endpoints[MAX_ENDPOINTS][MAX_URL_LEN];
int tts_count = 0;

// Simple JSON string extractor (finds "id":"value" patterns)
void extract_model_ids(const char *json, const char *endpoint) {
    const char *search = json;
    const char *id_marker = "\"id\":\"";

    while((search = strstr(search, id_marker)) != NULL && model_count < MAX_MODELS) {
        search += strlen(id_marker);
        const char *end = strchr(search, '"');
        if(end) {
            size_t len = end - search;
            if(len < MAX_MODEL_LEN) {
                strncpy(models[model_count].id, search, len);
                models[model_count].id[len] = '\0';

                // Filter out modelperm-* and chatcmpl-* entries
                if(strncmp(models[model_count].id, "modelperm-", 10) == 0 ||
                   strncmp(models[model_count].id, "chatcmpl-", 9) == 0) {
                    search = end + 1;
                    continue;
                }

                strncpy(models[model_count].endpoint, endpoint, MAX_URL_LEN-1);
                models[model_count].max_tokens = 8192; // Default
                printf("  - Discovered: %s\n", models[model_count].id);
                model_count++;
            }
        }
        search = end + 1;
    }
}

// Discover models from an endpoint
void discover_models_from_endpoint(SoupSession *session, const char *endpoint) {
    char url[MAX_URL_LEN];
    snprintf(url, sizeof(url), "%s/models", endpoint);

    printf("Discovering models from: %s\n", endpoint);

    SoupMessage *msg = soup_message_new("GET", url);
    if(!msg) return;

    GError *error = NULL;
    GInputStream *stream = soup_session_send(session, msg, NULL, &error);

    if(error) {
        g_error_free(error);
        g_object_unref(msg);
        return;
    }

    // Read response body
    GString *body = g_string_new(NULL);
    char buffer[4096];
    gssize read;

    while((read = g_input_stream_read(stream, buffer, sizeof(buffer)-1, NULL, NULL)) > 0) {
        buffer[read] = '\0';
        g_string_append(body, buffer);
    }

    extract_model_ids(body->str, endpoint);

    g_string_free(body, TRUE);
    g_object_unref(stream);
    g_object_unref(msg);
}

// Discover all models from environment variables
void discover_all_models(SoupSession *session) {
    printf("=== Model Discovery ===\n");

    // Discover chat/code models
    for(int i = 1; i < 10000; i++) {
        char var_name[32];
        snprintf(var_name, sizeof(var_name), "MODEL_ENDPOINT_%d", i);
        char *endpoint = getenv(var_name);
        if(!endpoint) break;
        discover_models_from_endpoint(session, endpoint);
    }

    // Discover TTS endpoints
    for(int i = 1; i < 10000; i++) {
        char var_name[32];
        snprintf(var_name, sizeof(var_name), "TTS_ENDPOINT_%d", i);
        char *endpoint = getenv(var_name);
        if(!endpoint) break;
        printf("Discovering TTS from: %s\n", endpoint);
        strncpy(tts_endpoints[tts_count++], endpoint, MAX_URL_LEN-1);
    }

    printf("\nTotal models discovered: %d\n", model_count);
    printf("Total TTS endpoints: %d\n\n", tts_count);
}

// Make a non-streaming chat request
int chat_request(SoupSession *session, int model_idx, const char *prompt) {
    char url[MAX_URL_LEN];
    char json[2048];

    snprintf(url, sizeof(url), "%s/chat/completions", models[model_idx].endpoint);
    snprintf(json, sizeof(json),
        "{\"model\":\"%s\","
        "\"messages\":[{\"role\":\"user\",\"content\":\"%s\"}],"
        "\"stream\":false,"
        "\"temperature\":0.7,"
        "\"max_tokens\":100}",
        models[model_idx].id, prompt);

    SoupMessage *msg = soup_message_new("POST", url);
    if(!msg) return -1;

    soup_message_set_request(msg, "application/json", SOUP_MEMORY_COPY, json, strlen(json));

    GError *error = NULL;
    GInputStream *stream = soup_session_send(session, msg, NULL, &error);

    if(error) {
        g_error_free(error);
        g_object_unref(msg);
        return -1;
    }

    // Read response
    char buffer[4096];
    gssize read = g_input_stream_read(stream, buffer, sizeof(buffer)-1, NULL, NULL);
    if(read > 0) {
        printf("Response received (%ld bytes)\n", read);
        printf("(Full response requires JSON parsing library)\n");
    }

    g_object_unref(stream);
    g_object_unref(msg);
    return 0;
}

// Streaming context for SSE parsing
typedef struct {
    char buffer[4096];
    size_t buffer_pos;
} StreamContext;

// Extract content from SSE data line
void extract_sse_content(const char *json_data, char *content, size_t content_size) {
    const char *content_marker = "\"content\":\"";
    const char *found = strstr(json_data, content_marker);
    if(found) {
        found += strlen(content_marker);
        const char *end = strchr(found, '"');
        if(end) {
            size_t len = end - found;
            if(len < content_size) {
                strncpy(content, found, len);
                content[len] = '\0';
            }
        }
    }
}

// Process streaming chunk
void process_stream_chunk(const char *data, size_t len, StreamContext *ctx) {
    for(size_t i = 0; i < len; i++) {
        if(data[i] == '\n') {
            ctx->buffer[ctx->buffer_pos] = '\0';

            // Process SSE line
            if(strncmp(ctx->buffer, "data: ", 6) == 0) {
                const char *json_data = ctx->buffer + 6;
                if(strcmp(json_data, "[DONE]") == 0) {
                    return; // Stop streaming
                }

                char content[1024] = {0};
                extract_sse_content(json_data, content, sizeof(content));
                if(strlen(content) > 0) {
                    printf("%s", content);
                    fflush(stdout);
                }
            }

            ctx->buffer_pos = 0;
        } else {
            if(ctx->buffer_pos < sizeof(ctx->buffer) - 1) {
                ctx->buffer[ctx->buffer_pos++] = data[i];
            }
        }
    }
}

// Streaming chat request
int chat_stream_request(SoupSession *session, int model_idx, const char *prompt) {
    char url[MAX_URL_LEN];
    char json[2048];

    snprintf(url, sizeof(url), "%s/chat/completions", models[model_idx].endpoint);
    snprintf(json, sizeof(json),
        "{\"model\":\"%s\","
        "\"messages\":[{\"role\":\"user\",\"content\":\"%s\"}],"
        "\"stream\":true,"
        "\"temperature\":0.7,"
        "\"max_tokens\":500}",
        models[model_idx].id, prompt);

    SoupMessage *msg = soup_message_new("POST", url);
    if(!msg) return -1;

    soup_message_set_request(msg, "application/json", SOUP_MEMORY_COPY, json, strlen(json));

    GError *error = NULL;
    GInputStream *stream = soup_session_send(session, msg, NULL, &error);

    if(error) {
        g_error_free(error);
        g_object_unref(msg);
        return -1;
    }

    // Read and process streaming chunks
    StreamContext ctx = {{0}, 0};
    char buffer[1024];
    gssize read;

    while((read = g_input_stream_read(stream, buffer, sizeof(buffer), NULL, NULL)) > 0) {
        process_stream_chunk(buffer, read, &ctx);
    }

    g_object_unref(stream);
    g_object_unref(msg);
    return 0;
}

int main(void) {
    printf("=== UncloseAI C Client (libsoup/GNOME) ===\n\n");

    // Initialize libsoup session
    SoupSession *session = soup_session_new();

    discover_all_models(session);

    if(model_count == 0) {
        printf("ERROR: No models discovered\n");
        g_object_unref(session);
        return 1;
    }

    // Non-streaming chat example
    printf("=== Non-Streaming Chat ===\n");
    printf("Model: %s\n", models[0].id);

    if(chat_request(session, 0, "Explain quantum computing in one sentence") != 0) {
        printf("Request failed\n");
    }

    printf("\n");

    // Streaming chat example
    int model_idx = (model_count >= 2) ? 1 : 0;
    printf("=== Streaming Chat ===\n");
    printf("Model: %s\n", models[model_idx].id);
    printf("Response: ");

    if(chat_stream_request(session, model_idx, "Write a hello world program in C") != 0) {
        printf("\nStreaming request failed\n");
    }

    printf("\n\n");

    // TTS example
    if(tts_count > 0) {
        printf("=== TTS Speech Generation ===\n");
        printf("Model: tts-1\n");

        char tts_url[MAX_URL_LEN];
        snprintf(tts_url, sizeof(tts_url), "%s/audio/speech", tts_endpoints[0]);

        const char *tts_json = "{"
            "\"model\":\"tts-1\","
            "\"voice\":\"alloy\","
            "\"input\":\"Hello from UncloseAI libsoup client!\""
            "}";

        SoupMessage *tts_msg = soup_message_new("POST", tts_url);
        if(tts_msg) {
            soup_message_set_request(tts_msg, "application/json", SOUP_MEMORY_COPY,
                                   tts_json, strlen(tts_json));

            GError *error = NULL;
            GInputStream *stream = soup_session_send(session, tts_msg, NULL, &error);

            if(!error) {
                FILE *fp = fopen("/tmp/speech.mp3", "wb");
                if(fp) {
                    char buffer[4096];
                    gssize read;
                    while((read = g_input_stream_read(stream, buffer, sizeof(buffer), NULL, NULL)) > 0) {
                        fwrite(buffer, 1, read, fp);
                    }
                    fclose(fp);
                    printf("Audio saved to /tmp/speech.mp3\n");
                } else {
                    printf("TTS failed: could not write file\n");
                }
                g_object_unref(stream);
            } else {
                printf("TTS failed: %s\n", error->message);
                g_error_free(error);
            }

            g_object_unref(tts_msg);
        }
    }

    printf("\n=== Examples Complete ===\n");

    g_object_unref(session);
    return 0;
}
