<?php

namespace AC_SYNC {
  if (!defined('ABSPATH'))
    exit; // Exit if accessed directly
  use AC_SYNC\Includes\Classes\Api\Action_Wear_Api;
  use AC_SYNC\Includes\Classes\Sql\Action_Wear_Sql_Manager;
  use AC_SYNC\Includes\Classes\Product\Action_Wear_Product as Product;
  use AC_SYNC\Includes\Classes\Product\Action_Wear_Product_Image as ProductImage;
  use AC_SYNC\Includes\Classes\Utils\Action_Wear_Utils as Utils;
  use AC_SYNC\Includes\Classes\Cron\Action_Wear_Cron as Cron;
  use AC_SYNC\Includes\Classes\Sql\Action_Wear_Sql_Versioning as Sql;
  use AC_SYNC\Includes\Classes\Category\Action_Wear_Category as Category;
  use AC_SYNC\Includes\Classes\Brand\Action_Wear_Brand as Brand;
  use AC_SYNC\Includes\Classes\Async\Action_Wear_Job_Runner as JobRunner;
  use AC_SYNC\Includes\Classes\Async\Action_Wear_Job_Scheduler as JobScheduler;
  use AC_SYNC\Includes\Classes\Async\Action_Wear_Job_Queue as JobQueue;
  use AC_SYNC\Includes\Classes\Order\Action_Wear_Order as Order;
  use AC_SYNC\Includes\Classes\Order\Action_Wear_Order_Sync as OrderSync;
  use AC_SYNC\Includes\Classes\Setting\Action_Wear_Setting_Order_Sync_Trigger as OrderSyncTrigger;
  use AC_SYNC\Includes\Classes\Setting\Action_Wear_Setting_Attributes_Box_Position as AttributesBoxPosition;
  use AC_SYNC\Includes\Classes\Async\Handlers as Handlers;

  class Action_Wear_Core
  {

    protected $utils;
    public static $plugin_dir;
    public static $plugin_file;
    public static $version;

    public function __construct(string $file)
    {
      self::$plugin_dir = dirname($file);
      self::$plugin_file = $file;
      $this->utils = new Utils();
    }

    public function on_plugin_activate()
    {
      $this->installTables();
      $this->createCamacPlaceholder();
      Category::checkDefaultCategoryExist();
      Product::checkDefaultAttributesCreation();
      if (get_option("_ACTIONWEAR_SUPPLIER_AVAILABILITY", false) === false)
        update_option("_ACTIONWEAR_SUPPLIER_AVAILABILITY", 0);
      if (get_option("_ACTIONWEAR_LAST_RESYNCED_PRODUCTS", false) === false)
        update_option("_ACTIONWEAR_LAST_RESYNCED_PRODUCTS", time());
    }

    /**
     * Crea il placeholder di Camac
     *
     * Crea il Placeholder necessario per identificare le immagini prodotto dentro la cartella uploads
     **/
    public function createCamacPlaceholder()
    {
      copy(dirname(__FILE__) . "/camac-placeholder.png", ProductImage::getPixelmagePath());
    }

    public function installTables()
    {
      Action_Wear_Sql_Manager::install();
    }

    public function rp_filter_js_files($file_string)
    {
      return pathinfo($file_string, PATHINFO_EXTENSION) === 'js';
    }

    public function rp_filter_css_files($file_string)
    {
      return pathinfo($file_string, PATHINFO_EXTENSION) === 'css';
    }

    public function rp_load_react_app($hook)
    {

      $is_main_dashboard = $hook === 'toplevel_page_action-wear';
      $is_product_page = $hook === "post.php";


      if ($is_product_page) {
        $id = isset($_GET["post"]) ? sanitize_text_field($_GET["post"]) : 0;

        // validate and cast id to number
        if ((int) $id > 0) {
          if (Product::isCamacProduct($id)) {
            add_action('admin_footer', [$this->utils, 'camac_product_js']);
          }
        }
      }

      if (!$is_main_dashboard)
        return;

      $plugin_app_dir_url = plugin_dir_url(__FILE__) . 'react-code/';
      $react_app_build = $plugin_app_dir_url . 'build/';
      $manifest_url = dirname(__FILE__) . '/react-code/build/asset-manifest.json';
      $request = file_get_contents($manifest_url);

      if (!$request)
        return false;

      $files_data = json_decode($request);
      if ($files_data === null)
        return;

      if (!property_exists($files_data, 'entrypoints'))
        return false;

      $assets_files = $files_data->entrypoints;

      $js_files = array_filter($assets_files, [$this, 'rp_filter_js_files']);
      $css_files = array_filter($assets_files, [$this, 'rp_filter_css_files']);

      foreach ($css_files as $index => $css_file) {
        wp_enqueue_style('react-code-' . $index, $react_app_build . $css_file);
      }

      foreach ($js_files as $index => $js_file) {
        wp_enqueue_script('react-code-' . $index, $react_app_build . $js_file, array(), 1, true);
      }
    }

    /**
     * Register custom cron interval: every 2 minutes for the job runner.
     */
    public function actionwear_add_cron_intervals($schedules)
    {
      $schedules['actionwear_two_minutes'] = array(
        'interval' => 120,
        'display' => esc_html__('Every 2 Minutes')
      );
      return $schedules;
    }

    public function remove_delete_image($content, $post_id, $thubmnail_id)
    {
      if (Product::isCamacThumbnail($thubmnail_id)) {
        $content = Utils::removeDeleteImageFromString($content);
        $content = Utils::removeClickEventFromSetThumbnail($content);
        $content = Utils::removeHintFromThumbnail($content);
      }
      return $content;
    }

    public function sv_wc_add_order_meta_box_action($actions)
    {
      $actions['wc_custom_order_action'] = "Esporta per ActionWear";
      return $actions;
    }

    /**
     * Run DB migrations and ensure recurring jobs are seeded.
     * Called on plugins_loaded.
     */
    public function init_processes()
    {
      Sql::getCurrentVersioningScript();

      if (JobQueue::isActionScheduler()) {
        $backend = JobQueue::getBackend();
        if (method_exists($backend, 'ensureRecurringJobs')) {
          $backend->ensureRecurringJobs();
        }
      } else {
        JobScheduler::ensureRecurringJobs();
      }
    }

    /**
     * Run the unified job runner.
     * Called by the actionwear_runner_hook cron event (every 120s).
     */
    public function runJobRunner()
    {
      $runner = new JobRunner();
      $runner->run();

      // Cleanup orphaned temp files (moved from old cron)
      Cron::cleanupOrphanedTempFiles();
    }

    /**
     * Weekly safety net: ensure recurring jobs still exist.
     * Called by the actionwear_scheduler_check cron event.
     */
    public static function weeklySchedulerCheck()
    {
      JobScheduler::ensureRecurringJobs();
    }

    public function initialize($version_number)
    {
      self::$version = $version_number;

      // actions
      add_action('admin_menu', [$this->utils, 'actionwear_menu']);
      add_action('admin_enqueue_scripts', [$this, 'rp_load_react_app']);
      add_action('actionwear_runner_hook', [$this, 'runJobRunner']);
      add_action('actionwear_scheduler_check', [self::class, 'weeklySchedulerCheck']);
      add_action('admin_bar_menu', [$this->utils, 'camac_top_bar'], 999);
      add_action('woocommerce_order_actions', [$this, 'sv_wc_add_order_meta_box_action']);
      add_action('woocommerce_order_action_wc_custom_order_action', [$this->utils, 'sv_wc_process_order_meta_box_action']);
      add_action('woocommerce_before_add_to_cart_button', [$this->utils, 'camac_before_add_to_cart']);
      $this->registerAttributesBoxHook();
      // Run on 'init' instead of 'plugins_loaded' to ensure WooCommerce
      // and Action Scheduler are fully loaded before we check the queue engine
      add_action('init', array($this, 'init_processes'));

      if (!JobQueue::isActionScheduler()) {
        add_action('admin_init', [$this, 'adminInitRunnerFallback']);
      }
      add_action('admin_footer', [$this->utils, 'actionwear_hard_reset_script']);
      add_action('woocommerce_process_product_meta', [$this->utils, 'actionwear_resync_product']);
      add_action('add_meta_boxes', [$this->utils, 'actionwear_product_metabox']);

      // prevent wp errors of php compression
      remove_action('shutdown', 'wp_ob_end_flush_all', 1);
      add_action('shutdown', function () {
        while (@ob_end_flush())
          ;
      });

      // filters
      add_filter('wp_get_attachment_url', [$this->utils, 'attach_cdn_urls'], 10, 2);
      add_filter('cron_schedules', [$this, 'actionwear_add_cron_intervals']);
      add_filter("admin_post_thumbnail_html", [$this, 'remove_delete_image'], 10, 3);
      add_filter('plugin_action_links_' . plugin_basename(self::$plugin_file), [$this->utils, 'actionwear_plugin_action_links']);

      if (!JobQueue::isActionScheduler()) {
        // Schedule runner cron (120s interval) — custom backend only
        if (!wp_next_scheduled('actionwear_runner_hook')) {
          wp_schedule_event(time(), 'actionwear_two_minutes', 'actionwear_runner_hook');
        }

        // Schedule weekly scheduler check — custom backend only
        if (!wp_next_scheduled('actionwear_scheduler_check')) {
          wp_schedule_event(time(), 'weekly', 'actionwear_scheduler_check');
        }
      } else {
        // Register Action Scheduler hooks
        $this->registerActionSchedulerHooks();
      }

      // execute on activate actions
      register_activation_hook(dirname(__FILE__) . "/actionwear.php", [$this, 'on_plugin_activate']);

      // intiialize api service
      Action_Wear_Api::init();

      // Order sync hook - register dynamic hook based on setting
      $this->registerOrderSyncHook();

      // Track orders containing ActionWear products when created
      add_action('woocommerce_new_order', [$this, 'onNewOrder'], 10, 1);
    }

    /**
     * Register WooCommerce order status hook for automatic order sync
     */
    private function registerOrderSyncHook()
    {
      $trigger = OrderSyncTrigger::getTriggerStatus();

      if ($trigger !== 'disabled') {
        add_action('woocommerce_order_status_' . $trigger, [$this, 'onOrderStatusChange'], 10, 2);
      }
    }

    /**
     * Register WooCommerce hook for attributes box display based on setting
     */
    private function registerAttributesBoxHook()
    {
      $hookConfig = AttributesBoxPosition::getSelectedHookConfig();

      if ($hookConfig['hook'] !== 'disabled') {
        add_action($hookConfig['hook'], [$this->utils, 'camac_display_product_attributes'], $hookConfig['priority']);
      }
    }

    /**
     * Register Action Scheduler hooks for all job types.
     * Only called when AS backend is active.
     */
    private function registerActionSchedulerHooks(): void
    {
      $handlerMap = [
        'product_import'              => Handlers\Action_Wear_Product_Import_Handler::class,
        'images_update'               => Handlers\Action_Wear_Images_Update_Handler::class,
        'bulk_price_fetch'            => Handlers\Action_Wear_Bulk_Price_Fetch_Handler::class,
        'bulk_price_batch'            => Handlers\Action_Wear_Bulk_Price_Batch_Handler::class,
        'bulk_quantity_fetch'         => Handlers\Action_Wear_Bulk_Quantity_Fetch_Handler::class,
        'bulk_quantity_batch'         => Handlers\Action_Wear_Bulk_Quantity_Batch_Handler::class,
        'diff_price'                  => Handlers\Action_Wear_Diff_Price_Handler::class,
        'diff_quantity'               => Handlers\Action_Wear_Diff_Quantity_Handler::class,
        'diff_catalog'                => Handlers\Action_Wear_Diff_Catalog_Handler::class,
        'catalog_generate_categories' => Handlers\Action_Wear_Catalog_Generate_Handler::class,
        'catalog_generate_products'   => Handlers\Action_Wear_Catalog_Generate_Handler::class,
        'catalog_generate_images'     => Handlers\Action_Wear_Catalog_Generate_Handler::class,
        'sku_cleanup'                 => Handlers\Action_Wear_Sku_Cleanup_Handler::class,
      ];

      foreach ($handlerMap as $jobType => $handlerClass) {
        add_action('actionwear/' . $jobType, function ($payload) use ($jobType, $handlerClass) {
          $handler = new $handlerClass();
          $job = (object) ['id' => 0, 'job_type' => $jobType, 'attempts' => 1, 'max_attempts' => 3];
          $payloadArray = is_array($payload) ? $payload : [];

          $result = $handler->handle($job, $payloadArray);

          if ($result->isRetry()) {
            throw new \Exception("Retry: " . $result->getReason());
          }

          if ($result->isFail()) {
            throw new \Exception("Failed: " . $result->getReason());
          }

          // Enqueue follow-up jobs
          foreach ($result->getFollowUpJobs() as $followUp) {
            JobQueue::push(
              $followUp['type'],
              $followUp['payload'] ?? null,
              $followUp['priority'] ?? 50,
              null,
              $followUp['conflict_group'] ?? null
            );
          }
        });
      }
    }

    /**
     * Fallback: run the job runner inline on admin page load.
     * If the WP cron loopback fails (firewall, DNS, reverse proxy),
     * this ensures jobs are processed when any admin visits a page.
     * Uses atomic CAS throttle to prevent concurrent dispatch.
     */
    public function adminInitRunnerFallback()
    {
      // Only run if there are pending jobs and runner appears stale or not running
      if (!JobQueue::hasPendingJobs()) return;

      if (JobRunner::isLocked() && !JobRunner::isStale()) return;

      // Atomic throttle: try to claim the dispatch slot
      if (!$this->claimAdminDispatchSlot()) return;

      $runner = new JobRunner();
      $runner->run();
    }

    /**
     * Atomically claim the admin dispatch slot using CAS UPDATE.
     * Only one concurrent process can succeed — prevents race conditions
     * where two admin page loads both trigger the runner.
     *
     * @return bool True if this process claimed the slot
     */
    private function claimAdminDispatchSlot(): bool
    {
      global $wpdb;

      $key = '_ACTIONWEAR_LAST_ADMIN_DISPATCH';
      $now = time();

      // Try atomic CAS: only update if enough time has passed (60s throttle)
      $updated = $wpdb->query($wpdb->prepare(
        "UPDATE {$wpdb->options} SET option_value = %s WHERE option_name = %s AND option_value < %s",
        $now,
        $key,
        $now - 60
      ));

      if ($updated > 0) {
        wp_cache_delete($key, 'options');
        return true;
      }

      // Row might not exist yet (first activation) — try INSERT IGNORE
      $wpdb->query($wpdb->prepare(
        "INSERT IGNORE INTO {$wpdb->options} (option_name, option_value, autoload) VALUES (%s, %s, 'no')",
        $key,
        $now
      ));

      wp_cache_delete($key, 'options');
      $current = get_option($key);
      return $current == $now;
    }

    /**
     * Callback when a new order is created - track if contains ActionWear products
     *
     * @param int $order_id WooCommerce order ID
     */
    public function onNewOrder($order_id)
    {
      if (Order::hasActionWearProducts($order_id)) {
        Order::trackOrder($order_id);
      }
    }

    /**
     * Callback when order status changes to the configured trigger status
     *
     * @param int $order_id WooCommerce order ID
     * @param \WC_Order $order WooCommerce order object
     */
    public function onOrderStatusChange($order_id, $order = null)
    {
      if (Order::hasActionWearProducts($order_id)) {
        Order::trackOrder($order_id);
        OrderSync::syncOrder($order_id);
      }
    }
  }
}
