<?php
/**
 * Plugin Name: OrbitSeeds Shared Seed Stock
 * Description: Controla o estoque de produtos variáveis (genéticas) por um total de sementes no produto pai. Variações são apenas "pacotes" que consomem N sementes do estoque geral.
 * Version: 1.1.0
 * Author: OrbitSeeds
 * Requires PHP: 7.4
 * Requires at least: 6.0
 * WC requires at least: 7.0
 *
 * Convenções de metadados:
 *   _seed_total_stock      (produto pai)  → total real de sementes
 *   _seed_stock_multiplier (variação)     → sementes consumidas por pacote
 *   _seed_shared_stock_reduced  (pedido)  → "yes" quando já abateu
 *   _seed_shared_stock_restored (pedido)  → "yes" quando já devolveu
 *
 * Regra: pacotes_disponiveis = floor(_seed_total_stock / _seed_stock_multiplier)
 */

if (!defined('ABSPATH')) { exit; }

class OrbitSeeds_Shared_Seed_Stock {

    const META_TOTAL = '_seed_total_stock';
    const META_MULT  = '_seed_stock_multiplier';
    const META_ORDER_REDUCED  = '_seed_shared_stock_reduced';
    const META_ORDER_RESTORED = '_seed_shared_stock_restored';

    public static function init() {
        $i = new self();
        // Cart / checkout validation
        add_filter('woocommerce_add_to_cart_validation', [$i, 'validate_add_to_cart'], 10, 5);
        add_action('woocommerce_check_cart_items', [$i, 'validate_cart_items']);
        add_action('woocommerce_checkout_process', [$i, 'validate_cart_items']);

        // Stock decrement: when WC reduces stock, we also do the seed-level decrement
        // and re-sync all sibling variations from the parent total.
        add_action('woocommerce_reduce_order_stock', [$i, 'reduce_seed_stock_for_order'], 10, 1);

        // Restore on cancel / restore actions
        add_action('woocommerce_restore_order_stock', [$i, 'restore_seed_stock_for_order'], 10, 1);
        add_action('woocommerce_order_status_cancelled', [$i, 'restore_seed_stock_for_order_id'], 10, 1);
        add_action('woocommerce_order_status_refunded',  [$i, 'restore_seed_stock_for_order_id'], 10, 1);

        // Refund partial
        add_action('woocommerce_order_refunded', [$i, 'on_order_refunded'], 10, 2);

        // REST endpoint
        add_action('rest_api_init', [$i, 'register_rest']);
    }

    // ---------- Helpers ----------

    /** Read total seeds for a parent product (int). */
    private function get_total($product_id) {
        $v = get_post_meta($product_id, self::META_TOTAL, true);
        return ($v === '' || $v === null) ? null : (int) $v;
    }

    private function set_total($product_id, $new_total) {
        update_post_meta($product_id, self::META_TOTAL, max(0, (int) $new_total));
    }

    /** Atomically subtract N from _seed_total_stock; returns true if it succeeded. */
    private function atomic_subtract($product_id, $amount) {
        global $wpdb;
        $amount = (int) $amount;
        if ($amount <= 0) return true;
        // UPDATE only if meta_value >= amount. CAST for safety.
        $sql = $wpdb->prepare(
            "UPDATE {$wpdb->postmeta}
             SET meta_value = CAST(meta_value AS UNSIGNED) - %d
             WHERE post_id = %d
               AND meta_key = %s
               AND CAST(meta_value AS UNSIGNED) >= %d",
            $amount, $product_id, self::META_TOTAL, $amount
        );
        $rows = $wpdb->query($sql);
        if ($rows) {
            wp_cache_delete($product_id, 'post_meta');
        }
        return $rows > 0;
    }

    private function atomic_add($product_id, $amount) {
        global $wpdb;
        $amount = (int) $amount;
        if ($amount <= 0) return true;
        $sql = $wpdb->prepare(
            "UPDATE {$wpdb->postmeta}
             SET meta_value = CAST(meta_value AS UNSIGNED) + %d
             WHERE post_id = %d AND meta_key = %s",
            $amount, $product_id, self::META_TOTAL
        );
        $rows = $wpdb->query($sql);
        if ($rows) {
            wp_cache_delete($product_id, 'post_meta');
        }
        return true;
    }

    /** Resolve multiplier for a variation (meta wins, else infer from attributes). */
    private function get_multiplier($variation_id) {
        $v = get_post_meta($variation_id, self::META_MULT, true);
        $n = ($v === '' || $v === null) ? 0 : (int) $v;
        if ($n > 0) return $n;
        // Infer from attribute slugs/names
        $variation = wc_get_product($variation_id);
        if (!$variation) return 0;
        $attrs = $variation->get_attributes();
        $candidates = [];
        foreach ($attrs as $name => $value) {
            $candidates[] = $value;
            if (preg_match('/(quantidade|qtd|sementes?|seeds?|pacote|unidades?)/i', $name)) {
                if (preg_match('/\d+/', $value, $m)) return (int) $m[0];
            }
        }
        foreach ($candidates as $val) {
            if (preg_match('/\d+/', $val, $m)) {
                $n = (int) $m[0];
                if ($n > 0) return $n;
            }
        }
        return 0;
    }

    /** True when a parent product is using the shared-seed system. */
    private function is_seed_managed($parent_id) {
        return $this->get_total($parent_id) !== null;
    }

    /** Recalculate stock_quantity / stock_status of every variation of a parent. */
    private function sync_variations($parent_id) {
        $total = (int) $this->get_total($parent_id);
        $product = wc_get_product($parent_id);
        if (!$product || !$product->is_type('variable')) return;
        foreach ($product->get_children() as $vid) {
            $mult = $this->get_multiplier($vid);
            if ($mult <= 0) continue;
            $avail = (int) floor($total / $mult);
            $v = wc_get_product($vid);
            if (!$v) continue;
            $v->set_manage_stock(true);
            $v->set_stock_quantity($avail);
            $v->set_stock_status($avail > 0 ? 'instock' : 'outofstock');
            // Force backorders off — shared stock must not allow sales beyond the seed total.
            $v->set_backorders('no');
            $v->save();
        }
        // Mirror parent status
        $p = wc_get_product($parent_id);
        if ($p) {
            $p->set_manage_stock(false);
            $p->set_stock_status($total > 0 ? 'instock' : 'outofstock');
            $p->save();
        }
    }

    // ---------- Cart / checkout ----------

    public function validate_add_to_cart($passed, $product_id, $quantity, $variation_id = 0, $variations = []) {
        if (!$passed || !$variation_id) return $passed;
        $parent_id = wp_get_post_parent_id($variation_id) ?: $product_id;
        if (!$this->is_seed_managed($parent_id)) return $passed;

        $mult = $this->get_multiplier($variation_id);
        if ($mult <= 0) return $passed;

        // Aggregate ALREADY in cart for the same parent
        $already = $this->seeds_requested_in_cart_for_parent($parent_id);
        $needed  = $already + ($quantity * $mult);
        $total   = (int) $this->get_total($parent_id);

        if ($needed > $total) {
            $product = wc_get_product($parent_id);
            wc_add_notice(sprintf(
                'Não há sementes suficientes em estoque para "%s". Restam %d sementes; este pedido precisaria de %d.',
                $product ? $product->get_name() : 'Produto',
                max(0, $total - $already),
                $quantity * $mult
            ), 'error');
            return false;
        }
        return $passed;
    }

    public function validate_cart_items() {
        if (!function_exists('WC') || !WC()->cart) return;
        $by_parent = [];
        foreach (WC()->cart->get_cart() as $item) {
            $parent_id = $item['product_id'];
            if (!$this->is_seed_managed($parent_id)) continue;
            $variation_id = (int) ($item['variation_id'] ?? 0);
            if (!$variation_id) continue;
            $mult = $this->get_multiplier($variation_id);
            if ($mult <= 0) continue;
            $by_parent[$parent_id] = ($by_parent[$parent_id] ?? 0) + ($item['quantity'] * $mult);
        }
        foreach ($by_parent as $parent_id => $seeds_needed) {
            $total = (int) $this->get_total($parent_id);
            if ($seeds_needed > $total) {
                $product = wc_get_product($parent_id);
                wc_add_notice(sprintf(
                    'Estoque insuficiente para "%s": disponíveis %d sementes, solicitado %d.',
                    $product ? $product->get_name() : 'Produto',
                    $total,
                    $seeds_needed
                ), 'error');
            }
        }
    }

    private function seeds_requested_in_cart_for_parent($parent_id) {
        if (!function_exists('WC') || !WC()->cart) return 0;
        $sum = 0;
        foreach (WC()->cart->get_cart() as $item) {
            if ((int) $item['product_id'] !== (int) $parent_id) continue;
            $vid = (int) ($item['variation_id'] ?? 0);
            $mult = $vid ? $this->get_multiplier($vid) : 0;
            if ($mult <= 0) continue;
            $sum += $item['quantity'] * $mult;
        }
        return $sum;
    }

    // ---------- Reduce / restore ----------

    /**
     * Per-item meta key storing seeds already restored back to the parent stock
     * for that line item. Lets us safely combine partial refunds with full
     * cancellation/refund of the order without double-restoring.
     */
    const ITEM_META_RESTORED = '_seed_seeds_restored';

    public function reduce_seed_stock_for_order($order) {
        if (!$order instanceof WC_Order) return;
        if ($order->get_meta(self::META_ORDER_REDUCED) === 'yes') return;

        $affected_parents = [];
        $errors = [];

        foreach ($order->get_items() as $item) {
            if (!$item instanceof WC_Order_Item_Product) continue;
            $variation_id = $item->get_variation_id();
            $product_id   = $item->get_product_id();
            if (!$variation_id) continue;
            if (!$this->is_seed_managed($product_id)) continue;
            $mult = $this->get_multiplier($variation_id);
            if ($mult <= 0) continue;
            $seeds = $mult * (int) $item->get_quantity();
            $ok = $this->atomic_subtract($product_id, $seeds);
            if (!$ok) {
                $errors[] = sprintf('Estoque insuficiente para o produto #%d (precisava de %d sementes).', $product_id, $seeds);
            } else {
                $affected_parents[$product_id] = true;
                // Initialize restored counter to 0 for this item.
                wc_update_order_item_meta($item->get_id(), self::ITEM_META_RESTORED, 0);
            }
        }

        if (!empty($errors)) {
            $order->update_status('on-hold', implode(' ', $errors));
            return;
        }

        foreach (array_keys($affected_parents) as $pid) {
            $this->sync_variations($pid);
        }

        $order->update_meta_data(self::META_ORDER_REDUCED, 'yes');
        $order->save();
    }

    public function restore_seed_stock_for_order_id($order_id) {
        $order = wc_get_order($order_id);
        if ($order) $this->restore_seed_stock_for_order($order);
    }

    /**
     * Full restoration (cancel / refund / WC restore). Returns only the seeds
     * that have not yet been returned by previous partial refunds.
     */
    public function restore_seed_stock_for_order($order) {
        if (!$order instanceof WC_Order) return;
        if ($order->get_meta(self::META_ORDER_REDUCED) !== 'yes') return;
        if ($order->get_meta(self::META_ORDER_RESTORED) === 'yes') return;

        $affected = [];
        foreach ($order->get_items() as $item) {
            if (!$item instanceof WC_Order_Item_Product) continue;
            $variation_id = $item->get_variation_id();
            $product_id   = $item->get_product_id();
            if (!$variation_id || !$this->is_seed_managed($product_id)) continue;
            $mult = $this->get_multiplier($variation_id);
            if ($mult <= 0) continue;
            $total_seeds    = $mult * (int) $item->get_quantity();
            $already        = (int) wc_get_order_item_meta($item->get_id(), self::ITEM_META_RESTORED, true);
            $remaining      = $total_seeds - $already;
            if ($remaining <= 0) continue;
            $this->atomic_add($product_id, $remaining);
            wc_update_order_item_meta($item->get_id(), self::ITEM_META_RESTORED, $already + $remaining);
            $affected[$product_id] = true;
        }
        foreach (array_keys($affected) as $pid) {
            $this->sync_variations($pid);
        }
        $order->update_meta_data(self::META_ORDER_RESTORED, 'yes');
        $order->save();
    }

    /**
     * Partial refund: restore only items refunded in this specific refund,
     * incrementing the per-item restored counter so a later full refund/cancel
     * does not double-restore.
     */
    public function on_order_refunded($order_id, $refund_id) {
        $refund = wc_get_order($refund_id);
        $order  = wc_get_order($order_id);
        if (!$refund || !$order) return;
        if ($order->get_meta(self::META_ORDER_REDUCED) !== 'yes') return;
        $flag = '_seed_shared_stock_refund_restored';
        if ($refund->get_meta($flag) === 'yes') return;

        // Build a map of refunded qty per original order item id.
        $refunded_by_item = [];
        foreach ($refund->get_items() as $r_item) {
            if (!$r_item instanceof WC_Order_Item_Product) continue;
            $orig_id = (int) $r_item->get_meta('_refunded_item_id');
            if (!$orig_id) continue;
            $qty = abs((int) $r_item->get_quantity());
            if ($qty <= 0) continue;
            $refunded_by_item[$orig_id] = ($refunded_by_item[$orig_id] ?? 0) + $qty;
        }

        $affected = [];
        foreach ($order->get_items() as $item) {
            if (!$item instanceof WC_Order_Item_Product) continue;
            $orig_id = $item->get_id();
            if (empty($refunded_by_item[$orig_id])) continue;
            $variation_id = $item->get_variation_id();
            $product_id   = $item->get_product_id();
            if (!$variation_id || !$this->is_seed_managed($product_id)) continue;
            $mult = $this->get_multiplier($variation_id);
            if ($mult <= 0) continue;
            $seeds_to_return = $mult * (int) $refunded_by_item[$orig_id];
            $already         = (int) wc_get_order_item_meta($orig_id, self::ITEM_META_RESTORED, true);
            $total_seeds     = $mult * (int) $item->get_quantity();
            $cap             = max(0, $total_seeds - $already);
            $seeds_to_return = min($seeds_to_return, $cap);
            if ($seeds_to_return <= 0) continue;
            $this->atomic_add($product_id, $seeds_to_return);
            wc_update_order_item_meta($orig_id, self::ITEM_META_RESTORED, $already + $seeds_to_return);
            $affected[$product_id] = true;
        }
        foreach (array_keys($affected) as $pid) {
            $this->sync_variations($pid);
        }
        $refund->update_meta_data($flag, 'yes');
        $refund->save();
    }


    // ---------- REST ----------

    public function register_rest() {
        register_rest_route('seed-stock/v1', '/product/(?P<id>\d+)', [
            'methods'  => 'GET',
            'callback' => [$this, 'rest_get_product'],
            'permission_callback' => '__return_true',
        ]);
    }

    public function rest_get_product($req) {
        $id = (int) $req['id'];
        $product = wc_get_product($id);
        if (!$product || !$product->is_type('variable')) {
            return new WP_Error('not_variable', 'Produto não é variável.', ['status' => 404]);
        }
        $total = (int) $this->get_total($id);
        $variations = [];
        foreach ($product->get_children() as $vid) {
            $mult = $this->get_multiplier($vid);
            $variations[] = [
                'id'                => $vid,
                'multiplier'        => $mult,
                'availablePackages' => $mult > 0 ? (int) floor($total / $mult) : 0,
            ];
        }
        return [
            'productId'      => $id,
            'seedTotalStock' => $total,
            'variations'     => $variations,
        ];
    }
}

OrbitSeeds_Shared_Seed_Stock::init();
