#include "r.h"
#include "db_utils.h"
#include "line.h"
#include "markdown.h"
#include "openai.h"
#include "tools.h"
#include "utils.h"

#include <locale.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>

volatile sig_atomic_t sigint_count = 0;
time_t first_sigint_time = 0;
bool SYNTAX_HIGHLIGHT_ENABLED = true;
bool API_MODE = false;

static void render(const char *content);
static bool openai_include(const char *path);
static char *get_prompt_from_stdin(char *prompt);
static char *get_prompt_from_args(int argc, char **argv);
static bool try_prompt(int argc, char *argv[]);
static void repl(void);
static void init(void);
static void handle_sigint(int sig);

char * get_env_string(){
    FILE *fp = popen("env", "r");
    if (fp == NULL) {
        perror("popen failed");
        return NULL;
    }

    size_t buffer_size = 1024;
    size_t total_size = 0;
    char *output = malloc(buffer_size);
    if (output == NULL) {
        perror("malloc failed");
        pclose(fp);
        return NULL;
    }

    size_t bytes_read;
    while ((bytes_read = fread(output + total_size, 1, buffer_size - total_size, fp)) > 0) {
        total_size += bytes_read;
        if (total_size >= buffer_size) {
            buffer_size *= 2;
            char *temp = realloc(output, buffer_size);
            if (temp == NULL) {
                perror("realloc failed");
                free(output);
                pclose(fp);
                return NULL;
            }
            output = temp;
        }
    }

    // Null-terminate the output
    output[total_size] = '\0';


    pclose(fp);
    return output;
}

static char *get_prompt_from_stdin(char *prompt) {
    int index = 0;
    int c;
    while ((c = getchar()) != EOF) {
        prompt[index++] = (char)c;
    }
    prompt[index] = '\0';
    return prompt;
}

static char *get_prompt_from_args(int argc, char **argv) {
    char *prompt = malloc(10 * 1024 * 1024 + 1);
    char *system = malloc(1024 * 1024);
    if (!prompt || !system) {
        fprintf(stderr, "Error: Memory allocation failed.\n");
        free(prompt);
        free(system);
        return NULL;
    }

    bool get_from_std_in = false;

    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "--stdin") == 0) {
            fprintf(stderr, "Reading from stdin.\n");
            get_from_std_in = true;
        } else if (strcmp(argv[i], "--verbose") == 0) {
            is_verbose = true;
        } else if (strcmp(argv[i], "--py") == 0 && i + 1 < argc) {
            char *py_file_path = expand_home_directory(argv[++i]);
            fprintf(stderr, "Including \"%s\".\n", py_file_path);
            openai_include(py_file_path);
            free(py_file_path);
        } else if (strcmp(argv[i], "--free") == 0) {
            auth_free();
        } else if (strcmp(argv[i], "--context") == 0 && i + 1 < argc) {
            char *context_file_path = argv[++i];
            fprintf(stderr, "Including \"%s\".\n", context_file_path);
            openai_include(context_file_path);
        } else if (strcmp(argv[i], "--api") == 0) {
            API_MODE = true;
        } else if (strcmp(argv[i], "--nh") == 0) {
            SYNTAX_HIGHLIGHT_ENABLED = false;
            fprintf(stderr, "Syntax highlighting disabled.\n");
        } else {
            strcat(system, argv[i]);
            strcat(system, (i < argc - 1) ? " " : ".");
        }
    }

    if (get_from_std_in) {
        if (*system)
            openai_system(system);
        prompt = get_prompt_from_stdin(prompt);
    } else {
        free(prompt);
        prompt = system;
    }

    if (!*prompt) {
        free(prompt);
        return NULL;
    }
    return prompt;
}

static bool try_prompt(int argc, char *argv[]) {
    char *prompt = get_prompt_from_args(argc, argv);
    if (prompt) {
        char *response = openai_chat("user", prompt);
        if (!response) {
            printf("Could not get response from server\n");
            free(prompt);
            return false;
        }
        render(response);
        free(response);
        free(prompt);
        return true;
    }
    return false;
}

static bool openai_include(const char *path) {
    char *file_content = read_file(path);
    if (!file_content)
        return false;

    openai_system(file_content);
    free(file_content);
    return true;
}

static void render(const char *content) {
    if (SYNTAX_HIGHLIGHT_ENABLED) {
        parse_markdown_to_ansi(content);
    } else {
        printf("%s", content);
    }
}

static void repl(void) {
    line_init();
    char *line = NULL;

    while (true) {
        line = line_read("> ");
        if (!line || !*line)
            continue;

        if (!strncmp(line, "!dump", 5)) {
            printf("%s\n", message_json());
            continue;
        }
        if (!strncmp(line, "!verbose", 8)) {
            is_verbose = !is_verbose;
            fprintf(stderr, "%s\n", is_verbose ? "Verbose mode enabled" : "Verbose mode disabled");
            continue;
        }
        if (line && *line != '\n')
            line_add_history(line);
        if (!strncmp(line, "!tools", 6)) {
            printf("Available tools: %s\n", json_object_to_json_string(tools_descriptions()));
            continue;
        }
        if (!strncmp(line, "!models", 7)) {
            printf("Current model: %s\n", openai_fetch_models());
            continue;
        }
        if (!strncmp(line, "!model", 6)) {
            if (line[6] == ' ') {
                set_prompt_model(line + 7);
            }
            printf("Current model: %s\n", get_prompt_model());
            continue;
        }
        if (!strncmp(line, "exit", 4))
            exit(0);

        while (line && *line != '\n') {
            char *response = openai_chat("user", line);
            if (response) {
                render(response);
                printf("\n");
                if (strstr(response, "_STEP_")) {
                    line = "continue";
                } else {
                    line = NULL;
                }
                free(response);
            } else {
                exit(0);
            }
        }
    }
}

static void init(void) {
    setbuf(stdout, NULL);
    line_init();
    auth_init();
    db_initialize();
    char *schema = db_get_schema();
    char payload[1024 * 1024] = {0};
    snprintf(payload, sizeof(payload),
            "# LOCAL DATABASE" 
            "Your have a local database that you can mutate using the query tool and "
             "the get and set tool."
             "If you set a value using the tool, make sure that the key is stemmed and lowercased to prevent double entries."
             "Dialect is sqlite. This is the schema in json format: %s. ",
             schema);
    free(schema);
    fprintf(stderr, "Loading... 📨");
    openai_system(payload);
    char * env_system_message = get_env_system_message();
    if(env_system_message && *env_system_message){
        openai_system(env_system_message);
        free(env_system_message);
    }
    if (!openai_include(".rcontext.txt")) {
        openai_include("~/.rcontext.txt");
    }
    fprintf(stderr, "\r                          \r");
}

static void handle_sigint(int sig) {
    time_t current_time = time(NULL);
    printf("\n");
    if (sigint_count == 0) {
        first_sigint_time = current_time;
        sigint_count++;
    } else {
        if (difftime(current_time, first_sigint_time) <= 1) {
            exit(0);
        } else {
            sigint_count = 1;
            first_sigint_time = current_time;
        }
    }
}

int main(int argc, char *argv[]) {
    signal(SIGINT, handle_sigint);

    init();
    char * env_string = get_env_string();
    if(env_string && *env_string){
        openai_system(env_string);
        free(env_string);
    }
    if (try_prompt(argc, argv))
        return 0;

    repl();
    return 0;
}
