/**
 * adblock.c - Adblocker implementation using WebKitUserContentFilter
 *
 * This implementation uses WebKit's content blocking API to efficiently
 * block ads and trackers based on filter lists.
 */

#include "adblock.h"
#include <json-glib/json-glib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Private structure definition */
struct _AdblockManager {
  WebKitUserContentFilterStore *store;
  GHashTable *filters;      /* filter_id -> WebKitUserContentFilter */
  GHashTable *filter_paths; /* filter_id -> file path */
  gboolean enabled;
  gchar *store_path;
  guint ref_count;
};

/* Async operation context */
typedef struct {
  AdblockManager *manager;
  AdblockCallback callback;
  gpointer user_data;
  gchar *filter_id;
  gchar *filter_path;
} AdblockAsyncContext;

/* Error domain */
GQuark adblock_error_quark(void) {
  return g_quark_from_static_string("adblock-error-quark");
}

/* Forward declarations */
static void adblock_async_context_free(AdblockAsyncContext *ctx);
static gchar *adblock_convert_to_json(const gchar *filter_list_content,
                                      GError **error);
static void filter_save_callback(WebKitUserContentFilterStore *store,
                                 GAsyncResult *result, gpointer user_data);

/**
 * adblock_manager_new:
 */
AdblockManager *adblock_manager_new(const gchar *store_path) {
  AdblockManager *manager = g_new0(AdblockManager, 1);

  if (store_path != NULL) {
    manager->store_path = g_strdup(store_path);
  } else {
    /* Use default path in user config directory */
    manager->store_path = g_build_filename(g_get_user_config_dir(), "leaf-web",
                                           "content-filters", NULL);
  }

  /* Create store directory if it doesn't exist */
  g_mkdir_with_parents(manager->store_path, 0755);

  /* Initialize the WebKit content filter store */
  GFile *store_file = g_file_new_for_path(manager->store_path);
  manager->store = webkit_user_content_filter_store_new(manager->store_path);
  g_object_unref(store_file);

  /* Initialize hash tables */
  manager->filters =
      g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
                            (GDestroyNotify)webkit_user_content_filter_unref);

  manager->filter_paths =
      g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);

  manager->enabled = TRUE;
  manager->ref_count = 1;

  g_print("[Adblock] Manager initialized with store path: %s\n",
          manager->store_path);

  return manager;
}

/**
 * adblock_manager_free:
 */
void adblock_manager_free(AdblockManager *manager) {
  if (manager == NULL)
    return;

  manager->ref_count--;
  if (manager->ref_count > 0)
    return;

  g_hash_table_destroy(manager->filters);
  g_hash_table_destroy(manager->filter_paths);

  if (manager->store != NULL) {
    g_object_unref(manager->store);
  }

  g_free(manager->store_path);
  g_free(manager);

  g_print("[Adblock] Manager freed\n");
}

/**
 * adblock_async_context_free:
 */
static void adblock_async_context_free(AdblockAsyncContext *ctx) {
  if (ctx == NULL)
    return;

  g_free(ctx->filter_id);
  g_free(ctx->filter_path);
  g_free(ctx);
}

/**
 * is_ascii_string:
 *
 * Checks if a string contains only ASCII characters (0-127).
 * Returns TRUE if all characters are ASCII, FALSE otherwise.
 */
static gboolean is_ascii_string(const gchar *str) {
  if (str == NULL)
    return FALSE;

  for (const gchar *p = str; *p != '\0'; p++) {
    if ((guchar)*p > 127) {
      return FALSE;
    }
  }

  return TRUE;
}

/**
 * adblock_convert_to_json:
 *
 * Converts AdBlock Plus format to WebKit content blocker JSON format.
 * This is a simplified converter that handles common patterns.
 * Skips patterns with non-ASCII characters as WebKit only supports ASCII.
 */
static gchar *adblock_convert_to_json(const gchar *filter_list_content,
                                      GError **error) {
  JsonBuilder *builder = json_builder_new();
  JsonGenerator *generator = json_generator_new();
  JsonNode *root;
  gchar *json_str;

  gchar **lines = g_strsplit(filter_list_content, "\n", -1);

  /* Statistics tracking */
  guint total_lines = 0;
  guint processed_rules = 0;
  guint skipped_non_ascii = 0;
  guint skipped_other = 0;

  json_builder_begin_array(builder);

  for (gsize i = 0; lines[i] != NULL; i++) {
    gchar *line = g_strstrip(lines[i]);
    total_lines++;

    /* Skip comments, empty lines, and element hiding rules */
    if (line[0] == '\0' || line[0] == '!' || line[0] == '[' ||
        strstr(line, "##") != NULL || strstr(line, "#@#") != NULL) {
      skipped_other++;
      continue;
    }

    /* Skip exception rules for now (starting with @@) */
    if (g_str_has_prefix(line, "@@")) {
      skipped_other++;
      continue;
    }

    /* Validate that the line contains only ASCII characters */
    if (!is_ascii_string(line)) {
      g_debug("[Adblock] Skipping non-ASCII pattern: %.50s%s", line,
              strlen(line) > 50 ? "..." : "");
      skipped_non_ascii++;
      continue;
    }

    /* Basic URL blocking rule */
    gboolean wildcard_start = g_str_has_prefix(line, "||");
    gchar *pattern = line;

    if (wildcard_start) {
      pattern = line + 2; /* Skip || */
    }

    /* Remove trailing wildcards and separators */
    gsize len = strlen(pattern);
    if (len > 0 && pattern[len - 1] == '^') {
      pattern = g_strndup(pattern, len - 1);
    } else {
      pattern = g_strdup(pattern);
    }

    /* Skip if pattern is too short or invalid */
    if (strlen(pattern) < 3) {
      g_free(pattern);
      skipped_other++;
      continue;
    }

    /* Build JSON rule */
    processed_rules++;
    json_builder_begin_object(builder);

    /* Trigger */
    json_builder_set_member_name(builder, "trigger");
    json_builder_begin_object(builder);

    json_builder_set_member_name(builder, "url-filter");
    /* Escape special regex characters and convert wildcards */
    gchar *escaped = g_regex_escape_string(pattern, -1);
    gchar *url_filter = g_strdup_printf(".*%s.*", escaped);
    json_builder_add_string_value(builder, url_filter);
    g_free(escaped);
    g_free(url_filter);

    json_builder_set_member_name(builder, "resource-type");
    json_builder_begin_array(builder);
    json_builder_add_string_value(builder, "image");
    json_builder_add_string_value(builder, "script");
    json_builder_add_string_value(builder, "style-sheet");
    json_builder_add_string_value(builder, "media");
    json_builder_add_string_value(builder, "raw");
    json_builder_end_array(builder);

    json_builder_end_object(builder); /* trigger */

    /* Action */
    json_builder_set_member_name(builder, "action");
    json_builder_begin_object(builder);
    json_builder_set_member_name(builder, "type");
    json_builder_add_string_value(builder, "block");
    json_builder_end_object(builder); /* action */

    json_builder_end_object(builder); /* rule */

    g_free(pattern);
  }

  json_builder_end_array(builder);

  /* Print conversion statistics */
  g_print("[Adblock] Filter conversion summary:\n");
  g_print("  Total lines: %u\n", total_lines);
  g_print("  Processed rules: %u\n", processed_rules);
  g_print("  Skipped (non-ASCII): %u\n", skipped_non_ascii);
  g_print("  Skipped (other): %u\n", skipped_other);

  root = json_builder_get_root(builder);
  json_generator_set_root(generator, root);
  json_generator_set_pretty(generator, TRUE);
  json_str = json_generator_to_data(generator, NULL);

  json_node_free(root);
  g_object_unref(generator);
  g_object_unref(builder);
  g_strfreev(lines);

  return json_str;
}

/**
 * filter_save_callback:
 */
static void filter_save_callback(WebKitUserContentFilterStore *store,
                                 GAsyncResult *result, gpointer user_data) {
  AdblockAsyncContext *ctx = (AdblockAsyncContext *)user_data;
  GError *error = NULL;

  WebKitUserContentFilter *filter =
      webkit_user_content_filter_store_save_finish(store, result, &error);

  if (filter != NULL) {
    /* Store the compiled filter */
    g_hash_table_insert(ctx->manager->filters, g_strdup(ctx->filter_id),
                        filter);

    g_print("[Adblock] Successfully compiled filter: %s\n", ctx->filter_id);

    /* Invoke user callback */
    if (ctx->callback != NULL) {
      ctx->callback(ctx->manager, TRUE, NULL, ctx->user_data);
    }
  } else {
    g_warning("[Adblock] Failed to compile filter %s: %s", ctx->filter_id,
              error ? error->message : "Unknown error");

    /* Invoke user callback with error */
    if (ctx->callback != NULL) {
      ctx->callback(ctx->manager, FALSE, error, ctx->user_data);
    }
  }

  if (error != NULL) {
    g_error_free(error);
  }

  adblock_async_context_free(ctx);
}

/**
 * adblock_manager_load_from_file:
 */
gboolean adblock_manager_load_from_file(AdblockManager *manager,
                                        const gchar *filter_list_path,
                                        const gchar *filter_identifier,
                                        AdblockCallback callback,
                                        gpointer user_data, GError **error) {
  if (manager == NULL || filter_list_path == NULL ||
      filter_identifier == NULL) {
    g_set_error(error, ADBLOCK_ERROR, ADBLOCK_ERROR_IO_ERROR,
                "Invalid parameters");
    return FALSE;
  }

  /* Read the filter list file */
  gchar *filter_content = NULL;
  gsize length = 0;

  if (!g_file_get_contents(filter_list_path, &filter_content, &length, error)) {
    g_prefix_error(error, "Failed to read filter list: ");
    return FALSE;
  }

  g_print("[Adblock] Loaded filter list from %s (%zu bytes)\n",
          filter_list_path, length);

  /* Convert to JSON format */
  GError *conversion_error = NULL;
  gchar *json_content =
      adblock_convert_to_json(filter_content, &conversion_error);
  g_free(filter_content);

  if (json_content == NULL) {
    g_set_error(error, ADBLOCK_ERROR, ADBLOCK_ERROR_INVALID_JSON,
                "Failed to convert filter list to JSON: %s",
                conversion_error ? conversion_error->message : "Unknown error");
    if (conversion_error != NULL) {
      g_error_free(conversion_error);
    }
    return FALSE;
  }

  g_print("[Adblock] Converted to JSON format, starting compilation...\n");

  /* Create async context */
  AdblockAsyncContext *ctx = g_new0(AdblockAsyncContext, 1);
  ctx->manager = manager;
  ctx->callback = callback;
  ctx->user_data = user_data;
  ctx->filter_id = g_strdup(filter_identifier);
  ctx->filter_path = g_strdup(filter_list_path);

  /* Store the filter path for later reloading */
  g_hash_table_insert(manager->filter_paths, g_strdup(filter_identifier),
                      g_strdup(filter_list_path));

  /* Compile and save the filter asynchronously */
  GBytes *json_bytes = g_bytes_new_take(json_content, strlen(json_content));

  webkit_user_content_filter_store_save(
      manager->store, filter_identifier, json_bytes, NULL,
      (GAsyncReadyCallback)filter_save_callback, ctx);

  g_bytes_unref(json_bytes);

  return TRUE;
}

/**
 * adblock_manager_apply_to_web_view:
 */
gboolean adblock_manager_apply_to_web_view(AdblockManager *manager,
                                           WebKitWebView *web_view,
                                           GError **error) {
  if (manager == NULL || web_view == NULL) {
    g_set_error(error, ADBLOCK_ERROR, ADBLOCK_ERROR_IO_ERROR,
                "Invalid parameters");
    return FALSE;
  }

  if (!manager->enabled) {
    g_print("[Adblock] Adblocking is disabled, skipping filter application\n");
    return TRUE;
  }

  WebKitUserContentManager *content_manager =
      webkit_web_view_get_user_content_manager(web_view);

  if (content_manager == NULL) {
    g_set_error(error, ADBLOCK_ERROR, ADBLOCK_ERROR_IO_ERROR,
                "Failed to get user content manager");
    return FALSE;
  }

  /* Apply all loaded filters */
  GHashTableIter iter;
  gpointer key, value;
  guint count = 0;

  g_hash_table_iter_init(&iter, manager->filters);
  while (g_hash_table_iter_next(&iter, &key, &value)) {
    WebKitUserContentFilter *filter = (WebKitUserContentFilter *)value;
    webkit_user_content_manager_add_filter(content_manager, filter);
    count++;
  }

  g_print("[Adblock] Applied %u filters to WebView\n", count);

  return TRUE;
}

/**
 * adblock_manager_set_enabled:
 */
void adblock_manager_set_enabled(AdblockManager *manager, gboolean enabled) {
  if (manager == NULL)
    return;

  manager->enabled = enabled;
  g_print("[Adblock] Ad blocking %s\n", enabled ? "enabled" : "disabled");
}

/**
 * adblock_manager_is_enabled:
 */
gboolean adblock_manager_is_enabled(AdblockManager *manager) {
  if (manager == NULL)
    return FALSE;

  return manager->enabled;
}

/**
 * adblock_manager_get_filter_count:
 */
guint adblock_manager_get_filter_count(AdblockManager *manager) {
  if (manager == NULL)
    return 0;

  return g_hash_table_size(manager->filters);
}

/**
 * Reload callback context
 */
typedef struct {
  AdblockManager *manager;
  AdblockCallback callback;
  gpointer user_data;
  guint total_filters;
  guint completed_filters;
  gboolean has_error;
  GError *last_error;
} ReloadContext;

static void reload_single_filter_callback(AdblockManager *manager,
                                          gboolean success, GError *error,
                                          gpointer user_data) {
  ReloadContext *ctx = (ReloadContext *)user_data;

  ctx->completed_filters++;

  if (!success && !ctx->has_error) {
    ctx->has_error = TRUE;
    if (error != NULL) {
      ctx->last_error = g_error_copy(error);
    }
  }

  /* Check if all filters are done */
  if (ctx->completed_filters >= ctx->total_filters) {
    if (ctx->callback != NULL) {
      ctx->callback(ctx->manager, !ctx->has_error, ctx->last_error,
                    ctx->user_data);
    }

    if (ctx->last_error != NULL) {
      g_error_free(ctx->last_error);
    }
    g_free(ctx);
  }
}

/**
 * adblock_manager_reload_filters:
 */
void adblock_manager_reload_filters(AdblockManager *manager,
                                    AdblockCallback callback,
                                    gpointer user_data) {
  if (manager == NULL)
    return;

  guint filter_count = g_hash_table_size(manager->filter_paths);

  if (filter_count == 0) {
    g_print("[Adblock] No filters to reload\n");
    if (callback != NULL) {
      callback(manager, TRUE, NULL, user_data);
    }
    return;
  }

  g_print("[Adblock] Reloading %u filters...\n", filter_count);

  /* Create reload context */
  ReloadContext *ctx = g_new0(ReloadContext, 1);
  ctx->manager = manager;
  ctx->callback = callback;
  ctx->user_data = user_data;
  ctx->total_filters = filter_count;
  ctx->completed_filters = 0;
  ctx->has_error = FALSE;
  ctx->last_error = NULL;

  /* Clear existing filters */
  g_hash_table_remove_all(manager->filters);

  /* Reload each filter */
  GHashTableIter iter;
  gpointer key, value;

  g_hash_table_iter_init(&iter, manager->filter_paths);
  while (g_hash_table_iter_next(&iter, &key, &value)) {
    const gchar *filter_id = (const gchar *)key;
    const gchar *filter_path = (const gchar *)value;

    GError *error = NULL;
    adblock_manager_load_from_file(manager, filter_path, filter_id,
                                   reload_single_filter_callback, ctx, &error);

    if (error != NULL) {
      g_warning("[Adblock] Failed to reload filter %s: %s", filter_id,
                error->message);
      reload_single_filter_callback(manager, FALSE, error, ctx);
      g_error_free(error);
    }
  }
}
