<?php

namespace Kanzu\StoreVoiceAI\Services;

// Prevent direct file access.
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Notifies the backend whenever a WooCommerce product is created, updated,
 * trashed, or permanently deleted, so it can re-pull the affected product
 * via the WooCommerce REST API key it already holds.
 *
 * The notification is enqueued through Action Scheduler (bundled with
 * WooCommerce, which is a hard plugin dependency) so the product save
 * itself stays fast — the HTTP call runs on a later request handled by
 * the WC queue runner.
 */
class Product_Sync
{
    private const ASYNC_HOOK = 'storevoiceai_sync_product_catalog';
    private const AS_GROUP   = 'storevoiceai';

    /**
     * Per-request map of product_id => action already queued. Used to
     * collapse the repeated woocommerce_update_product fires (and the
     * new+update double-fire on create) into one notify per product.
     * 'deleted' supersedes a prior 'created'/'updated' for the same id.
     *
     * @var array<int,string>
     */
    private array $queued = [];

    public function register_hooks(): void
    {
        // WooCommerce CRUD hooks — cover admin edits, WC REST writes, and any
        // code path that uses $product->save() / $product->delete(). They skip
        // revisions and autosaves natively, so no save_post-style guards needed.
        add_action('woocommerce_new_product',    [$this, 'on_created'], 10, 1);
        add_action('woocommerce_update_product', [$this, 'on_updated'], 10, 1);
        add_action('woocommerce_trash_product',  [$this, 'on_deleted'], 10, 1);
        add_action('woocommerce_delete_product', [$this, 'on_deleted'], 10, 1);

        // Async worker — invoked later by the Action Scheduler queue runner.
        add_action(self::ASYNC_HOOK, [$this, 'dispatch'], 10, 2);
    }

    public function on_created($product_id): void
    {
        $this->enqueue((int) $product_id, 'created');
    }

    public function on_updated($product_id): void
    {
        $this->enqueue((int) $product_id, 'updated');
    }

    public function on_deleted($product_id): void
    {
        $this->enqueue((int) $product_id, 'deleted');
    }

    private function enqueue(int $product_id, string $action): void
    {
        if ($product_id <= 0) {
            return;
        }

        // Nothing useful we can do until the site is connected and we have a token.
        // We re-check this on dispatch too, in case it changes between enqueue
        // and execution.
        if (!get_option('_storevoiceai_site_registered') || !get_option('_storevoiceai_access_token')) {
            return;
        }

        // Collapse repeats within this request. Deletion wins over upsert
        // so we don't waste a notify telling the backend to pull a product
        // that's about to be removed.
        if (isset($this->queued[$product_id])) {
            if ($this->queued[$product_id] === 'deleted') {
                return;
            }
            if ($action !== 'deleted') {
                return;
            }
        }
        $args = [$product_id, $action];

        if (function_exists('as_enqueue_async_action')) {
            if ($action !== 'deleted'
                && function_exists('as_has_scheduled_action')
                && as_has_scheduled_action(self::ASYNC_HOOK, [$product_id, 'deleted'], self::AS_GROUP)) {
                return;
            }

            if ($action === 'deleted') {
                $this->unschedule_upserts($product_id);
            }

            $this->queued[$product_id] = $action;

            // Don't pile up pending duplicates if the queue runner is behind.
            if (function_exists('as_has_scheduled_action')
                && as_has_scheduled_action(self::ASYNC_HOOK, $args, self::AS_GROUP)) {
                return;
            }
            as_enqueue_async_action(self::ASYNC_HOOK, $args, self::AS_GROUP);
            return;
        }

        // Action Scheduler should always be present (WooCommerce bundles it),
        // but fall back to a synchronous send rather than silently dropping
        // the notify if something has gone wrong with the queue.
        $this->queued[$product_id] = $action;
        $this->dispatch($product_id, $action);
    }

    private function unschedule_upserts(int $product_id): void
    {
        if (!function_exists('as_unschedule_action')) {
            return;
        }

        foreach (['created', 'updated'] as $stale_action) {
            $args = [$product_id, $stale_action];
            while (!function_exists('as_has_scheduled_action') || as_has_scheduled_action(self::ASYNC_HOOK, $args, self::AS_GROUP)) {
                $unscheduled = as_unschedule_action(self::ASYNC_HOOK, $args, self::AS_GROUP);
                if (!$unscheduled) {
                    break;
                }
            }
        }
    }

    /**
     * Action Scheduler callback. Runs on a later request via the queue runner.
     */
    public function dispatch($product_id, $action): void
    {
        $product_id = (int) $product_id;
        $action     = (string) $action;

        if ($product_id <= 0 || $action === '') {
            return;
        }

        if (!get_option('_storevoiceai_site_registered') || !get_option('_storevoiceai_access_token')) {
            return;
        }

        $api    = new Store_Voice_AI_API();
        $result = $api->sync_product_catalog([
            'action'    => $action,
            'productId' => $product_id,
        ]);

        if ($result === false || is_wp_error($result)) {
            $logger = \Kanzu\StoreVoiceAI\Kanzu_StoreVoiceAI()->container->get('logger');
            $context = array(
                'action'    => $action,
                'productId' => $product_id,
            );

            if (is_wp_error($result)) {
                $context['code']    = $result->get_error_code();
                $context['message'] = $result->get_error_message();
            } else {
                $context['message'] = 'Product catalog sync returned false.';
            }

            $logger->error('product_catalog.sync.dispatch_failed', $context);
            throw new \RuntimeException($context['message']);
        }
    }
}
