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

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

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

void help();
void render(const char *);
bool openai_include(const char *);
char *strreplace(const char *, const char *, const char *);

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

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;
}

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;
}

char **get_parameters(const char *content, const char *delimiter) {
  char *start = NULL;
  char **parameters = NULL;
  int count = 0;

  while ((start = strstr(content, delimiter)) != NULL) {
    start += 3;
    char *end = strstr(start, delimiter);
    char *parameter = malloc(end - start + 1);

    memcpy(parameter, start, end - start);
    parameter[end - start] = '\0';

    content = end + 3;
    count++;
    parameters = realloc(parameters, sizeof(char *) * (count + 1));
    parameters[count - 1] = parameter;
    parameters[count] = NULL;
  }

  return parameters;
}

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

void repl() {
  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);
      }
    }
  }
}

char *strreplace(const char *content, const char *what, const char *with) {
  char *pos = strstr(content, what);
  if (!pos)
    return strdup(content);

  size_t result_size = strlen(content) + strlen(with) - strlen(what) + 1;
  char *result = malloc(result_size);
  snprintf(result, result_size, "%.*s%s%s", (int)(pos - content), content, with,
           pos + strlen(what));
  return result;
}

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;
}

void init() {
  setbuf(stdout, NULL);
  line_init();
  auth_init();
  db_initialize();
  char *schema = db_get_schema();
  char payload[1024 * 1024] = {0};
  snprintf(payload, sizeof(payload),
           "Your have a database that you can mutate using the query tool and "
           "the get and set tool. This is the schema in json format: %s. "
           "Dialect is sqlite.",
           schema);
  free(schema);

  fprintf(stderr, "Loading... 4e6");
  openai_system(payload);
  if (!openai_include(".rcontext.txt")) {
    openai_include("~/.rcontext.txt");
  }
  fprintf(stderr, "\r                          \r");
}

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();
  if (try_prompt(argc, argv))
    return 0;

  repl();
  return 0;
}
