#include "downloads/download_manager.h"
#include <curl/curl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

// Thread data structure to pass both part and download
typedef struct {
  DownloadPart *part;
  Download *download;
} ThreadData;

static GList *active_downloads = NULL;
static GMutex downloads_mutex;

// Callback for curl to write data to file
static size_t write_data_callback(void *ptr, size_t size, size_t nmemb,
                                  void *userdata) {
  FILE *fp = (FILE *)userdata;
  return fwrite(ptr, size, nmemb, fp);
}

// Download a single part using HTTP range request
static gpointer download_part_thread(gpointer data) {
  ThreadData *thread_data = (ThreadData *)data;
  DownloadPart *part = thread_data->part;
  Download *download = thread_data->download;

  CURL *curl = curl_easy_init();
  if (!curl) {
    return NULL;
  }

  FILE *fp = fopen(part->temp_file, "wb");
  if (!fp) {
    curl_easy_cleanup(curl);
    return NULL;
  }

  // Set range header
  char range[256];
  snprintf(range, sizeof(range), "%zu-%zu", part->start_byte, part->end_byte);

  curl_easy_setopt(curl, CURLOPT_URL, download->url);
  curl_easy_setopt(curl, CURLOPT_RANGE, range);
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data_callback);
  curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
  curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);

  CURLcode res = curl_easy_perform(curl);

  fclose(fp);
  curl_easy_cleanup(curl);

  // Clean up thread data
  g_free(thread_data);

  if (res == CURLE_OK && !download->cancel_requested) {
    g_mutex_lock(&download->mutex);
    part->completed = TRUE;
    part->downloaded = part->end_byte - part->start_byte + 1;
    download->downloaded += part->downloaded;
    g_mutex_unlock(&download->mutex);
  }

  return NULL;
}

// Merge downloaded parts into final file
static gboolean merge_parts(Download *download) {
  FILE *output = fopen(download->destination, "wb");
  if (!output) {
    return FALSE;
  }

  for (int i = 0; i < download->num_parts; i++) {
    FILE *part_file = fopen(download->parts[i]->temp_file, "rb");
    if (!part_file) {
      fclose(output);
      return FALSE;
    }

    char buffer[8192];
    size_t bytes_read;
    while ((bytes_read = fread(buffer, 1, sizeof(buffer), part_file)) > 0) {
      fwrite(buffer, 1, bytes_read, output);
    }

    fclose(part_file);
    unlink(download->parts[i]->temp_file); // Delete temp file
  }

  fclose(output);
  return TRUE;
}

// Get file size using HEAD request
static size_t get_file_size(const char *url) {
  CURL *curl = curl_easy_init();
  if (!curl) {
    return 0;
  }

  curl_easy_setopt(curl, CURLOPT_URL, url);
  curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);

  CURLcode res = curl_easy_perform(curl);

  curl_off_t content_length = 0;
  if (res == CURLE_OK) {
    curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T,
                      &content_length);
  }

  curl_easy_cleanup(curl);
  return (size_t)content_length;
}

void download_manager_init(void) {
  curl_global_init(CURL_GLOBAL_ALL);
  g_mutex_init(&downloads_mutex);
}

Download *download_start(const char *url, const char *destination,
                         int num_parts) {
  if (!url || !destination || num_parts < 1 || num_parts > 32) {
    return NULL;
  }

  // Validate num_parts is power of 2
  if ((num_parts & (num_parts - 1)) != 0) {
    return NULL;
  }

  Download *download = g_new0(Download, 1);
  download->url = g_strdup(url);
  download->destination = g_strdup(destination);
  download->num_parts = num_parts;
  download->state = DOWNLOAD_STATE_PENDING;
  download->last_time = g_get_monotonic_time();
  g_mutex_init(&download->mutex);

  // Extract filename from URL
  const char *last_slash = strrchr(url, '/');
  download->filename = g_strdup(last_slash ? last_slash + 1 : url);

  // Get file size
  download->total_size = get_file_size(url);
  if (download->total_size == 0) {
    g_print(
        "Warning: Could not determine file size, using single-part download\n");
    download->num_parts = 1;
  }

  // Create download parts
  download->parts = g_new0(DownloadPart *, num_parts);
  size_t part_size = download->total_size / num_parts;

  for (int i = 0; i < num_parts; i++) {
    DownloadPart *part = g_new0(DownloadPart, 1);
    part->part_number = i;
    part->start_byte = i * part_size;
    part->end_byte = (i == num_parts - 1) ? download->total_size - 1
                                          : (i + 1) * part_size - 1;
    part->temp_file = g_strdup_printf("%s.part%d", destination, i);
    part->completed = FALSE;

    download->parts[i] = part;
  }

  // Add to active downloads
  g_mutex_lock(&downloads_mutex);
  active_downloads = g_list_append(active_downloads, download);
  g_mutex_unlock(&downloads_mutex);

  // Start download threads
  download->state = DOWNLOAD_STATE_DOWNLOADING;
  for (int i = 0; i < num_parts; i++) {
    ThreadData *thread_data = g_new0(ThreadData, 1);
    thread_data->part = download->parts[i];
    thread_data->download = download;
    download->parts[i]->thread =
        g_thread_new(NULL, download_part_thread, thread_data);
  }

  return download;
}

Download *download_start_webkit(WebKitDownload *webkit_download,
                                int num_parts) {
  if (!webkit_download) {
    return NULL;
  }

  WebKitURIRequest *request = webkit_download_get_request(webkit_download);
  const char *url = webkit_uri_request_get_uri(request);
  const char *destination = webkit_download_get_destination(webkit_download);

  if (!url || !destination) {
    return NULL;
  }

  Download *download = download_start(url, destination, num_parts);
  if (download) {
    download->webkit_download = g_object_ref(webkit_download);
  }

  return download;
}

void download_pause(Download *download) {
  if (!download || download->state != DOWNLOAD_STATE_DOWNLOADING) {
    return;
  }

  g_mutex_lock(&download->mutex);
  download->pause_requested = TRUE;
  download->state = DOWNLOAD_STATE_PAUSED;
  g_mutex_unlock(&download->mutex);

  // Wait for threads to finish current chunks
  for (int i = 0; i < download->num_parts; i++) {
    if (download->parts[i]->thread && !download->parts[i]->completed) {
      g_thread_join(download->parts[i]->thread);
      download->parts[i]->thread = NULL;
    }
  }
}

void download_resume(Download *download) {
  if (!download || download->state != DOWNLOAD_STATE_PAUSED) {
    return;
  }

  g_mutex_lock(&download->mutex);
  download->pause_requested = FALSE;
  download->state = DOWNLOAD_STATE_DOWNLOADING;
  g_mutex_unlock(&download->mutex);

  // Restart incomplete parts
  for (int i = 0; i < download->num_parts; i++) {
    if (!download->parts[i]->completed) {
      ThreadData *thread_data = g_new0(ThreadData, 1);
      thread_data->part = download->parts[i];
      thread_data->download = download;
      download->parts[i]->thread =
          g_thread_new(NULL, download_part_thread, thread_data);
    }
  }
}

void download_cancel(Download *download) {
  if (!download) {
    return;
  }

  g_mutex_lock(&download->mutex);
  download->cancel_requested = TRUE;
  download->state = DOWNLOAD_STATE_CANCELLED;
  g_mutex_unlock(&download->mutex);

  // Wait for all threads to finish
  for (int i = 0; i < download->num_parts; i++) {
    if (download->parts[i]->thread) {
      g_thread_join(download->parts[i]->thread);
      download->parts[i]->thread = NULL;
    }
  }

  // Delete temp files
  for (int i = 0; i < download->num_parts; i++) {
    if (download->parts[i]->temp_file) {
      unlink(download->parts[i]->temp_file);
    }
  }
}

void download_free(Download *download) {
  if (!download) {
    return;
  }

  // Remove from active downloads
  g_mutex_lock(&downloads_mutex);
  active_downloads = g_list_remove(active_downloads, download);
  g_mutex_unlock(&downloads_mutex);

  g_free(download->url);
  g_free(download->destination);
  g_free(download->filename);

  if (download->webkit_download) {
    g_object_unref(download->webkit_download);
  }

  for (int i = 0; i < download->num_parts; i++) {
    g_free(download->parts[i]->temp_file);
    g_free(download->parts[i]);
  }
  g_free(download->parts);

  g_mutex_clear(&download->mutex);
  g_free(download);
}

GList *download_manager_get_downloads(void) {
  g_mutex_lock(&downloads_mutex);
  GList *downloads = g_list_copy(active_downloads);
  g_mutex_unlock(&downloads_mutex);
  return downloads;
}

// Background thread to monitor downloads and merge when complete
static gpointer download_monitor_thread(gpointer data) {
  while (TRUE) {
    g_usleep(500000); // Check every 500ms

    g_mutex_lock(&downloads_mutex);
    GList *iter = active_downloads;
    gint64 current_time = g_get_monotonic_time();

    while (iter) {
      Download *download = (Download *)iter->data;
      iter = iter->next;

      if (download->state != DOWNLOAD_STATE_DOWNLOADING) {
        continue;
      }

      // Calculate speed and ETA
      gint64 time_diff = current_time - download->last_time;
      if (time_diff > 1000000) { // Update every second
        g_mutex_lock(&download->mutex);
        size_t bytes_diff = download->downloaded - download->last_downloaded;
        download->speed = (double)bytes_diff / ((double)time_diff / 1000000.0);

        if (download->speed > 0) {
          size_t remaining = download->total_size - download->downloaded;
          download->eta = (double)remaining / download->speed;
        } else {
          download->eta = 0;
        }

        download->last_downloaded = download->downloaded;
        download->last_time = current_time;
        g_mutex_unlock(&download->mutex);
      }

      // Check if all parts are complete
      gboolean all_complete = TRUE;
      for (int i = 0; i < download->num_parts; i++) {
        if (!download->parts[i]->completed) {
          all_complete = FALSE;
          break;
        }
      }

      if (all_complete) {
        g_mutex_unlock(&downloads_mutex);

        // Merge parts
        if (merge_parts(download)) {
          g_mutex_lock(&download->mutex);
          download->state = DOWNLOAD_STATE_COMPLETED;
          g_mutex_unlock(&download->mutex);
          g_print("Download completed: %s\n", download->filename);
        } else {
          g_mutex_lock(&download->mutex);
          download->state = DOWNLOAD_STATE_FAILED;
          g_mutex_unlock(&download->mutex);
          g_print("Download failed (merge error): %s\n", download->filename);
        }

        g_mutex_lock(&downloads_mutex);
      }
    }
    g_mutex_unlock(&downloads_mutex);
  }

  return NULL;
}

// Auto-start monitor thread
__attribute__((constructor)) static void start_monitor_thread(void) {
  g_thread_new("download-monitor", download_monitor_thread, NULL);
}
