#include <buf.h>
#include <git2/diff.h>
#include "git_repo.h"
#include "git_repo_commit.h"

#include "../templ/duc_git_repo_commit.h"

static orb_rc_t fetch(orb_module_t *self) {
  duc_git_repo_commit_t *data = self->data;
  git_repo_commit_param_list_t *param_list = self->param_list;
  if (!param_list) return ORB_FATAL(1);
  if (!param_list->repo) return ORB_FATAL(2);
  if (!param_list->repo->flag_oid) return ORB_FATAL(3);
  data->repo = param_list->repo;
  git_oid commit_oid;
  if (git_oid_fromstr(&commit_oid, data->repo->flag_oid) != 0) return ORB_FATAL(4);
  git_commit *commit_handle = NULL;
  git_commit *parent_commit_handle = NULL;
  git_commit_lookup(&commit_handle, data->repo->handle, &commit_oid);
  data->commit = calloc(1, sizeof(git_repo_commit_t));
  git_repo_commit_t *commit = data->commit;
  commit->oid = strdup(data->repo->flag_oid);
  commit->message = strdup(git_commit_message(commit_handle));
  commit->summary = strdup(git_commit_summary(commit_handle));
  const char *body = git_commit_body(commit_handle);
  if (body) commit->body = strdup(body);
  commit->author_name = strdup(git_commit_author(commit_handle)->name);
  commit->author_email = strdup(git_commit_author(commit_handle)->email);
  commit->time = (time_t) git_commit_time(commit_handle);
  if (git_commit_parentcount(commit_handle) > 0) {
    git_commit_parent(&parent_commit_handle, commit_handle, 0);
    const git_oid *parent_oid = git_commit_id(parent_commit_handle);
    data->parent_commit_oid = malloc(sizeof(char) * (GIT_OID_HEXSZ + 1));
    git_oid_tostr(data->parent_commit_oid, GIT_OID_HEXSZ + 1, parent_oid);
  }
  git_tree *tree_handle = NULL;
  git_tree *parent_tree_handle = NULL;
  git_commit_tree(&tree_handle, commit_handle);
  if (parent_commit_handle != NULL) {
    git_commit_tree(&parent_tree_handle, parent_commit_handle);
  }
  git_diff *diff_handle = NULL;
  git_diff_tree_to_tree(&diff_handle, data->repo->handle, parent_tree_handle, tree_handle, NULL);
  size_t num_deltas = git_diff_num_deltas(diff_handle);
  buf_t out = BUF_NULL;
  for (size_t i = 0; i < num_deltas; i++) {
    git_patch *patch_handle = NULL;
    git_patch_from_diff(&patch_handle, diff_handle, i);
    git_buf buf_handle = GIT_BUF_INIT_CONST(NULL, 0);
    git_patch_to_buf(&buf_handle, patch_handle);
    buf_write_len(&out, buf_handle.ptr, buf_handle.size);
    buf_write(&out, "\n\n");
    git_buf_dispose(&buf_handle);
    git_patch_free(patch_handle);
  }
  buf_write_len(&out, "\0", 1);
  // git_diff_stats *stats_handle = NULL;
  // if (git_diff_get_stats(&stats_handle, diff_handle)) {
  //   size_t files_changed = git_diff_stats_files_changed(stats_handle);
  //   size_t insertions = git_diff_stats_insertions(stats_handle);
  //   size_t deletions = git_diff_stats_deletions(stats_handle);
  //   git_diff_stats_free(stats_handle);
  // }
  git_diff_free(diff_handle);
  git_tree_free(tree_handle);
  if (parent_tree_handle) {
    git_tree_free(parent_tree_handle);
  }
  git_commit_free(commit_handle);
  if (parent_commit_handle) {
    git_commit_free(parent_commit_handle);
  }
  data->diff = (char *) out.bytes;
  return ORB_OK;
}

static orb_rc_t render(orb_module_t *self, void *out) {
  return ORB_ERR(duc_git_repo_commit_render(out, (duc_git_repo_commit_t *) self->data));
}

static void cleanup(orb_module_t *self) {
  duc_git_repo_commit_t *data = self->data;
  if (data->commit) {
    git_repo_commit_t *commit = data->commit;
    free(commit->oid);
    free(commit->message);
    free(commit->summary);
    if (commit->body) free(commit->body);
    free(commit->author_name);
    free(commit->author_email);
    free(commit);
    commit = NULL;
  }
  if (data->parent_commit_oid) {
    free(data->parent_commit_oid);
    data->parent_commit_oid = NULL;
  }
  if (data->diff) {
    free(data->diff);
    data->diff = NULL;
  }
}

const orb_module_type_t GIT_REPO_COMMIT_TYPE = {
  .size = sizeof(duc_git_repo_commit_t),
  .fetch = fetch,
  .render = render,
.cleanup = cleanup,
};
