<?php
declare(strict_types=1);

namespace Help4BuilderSuite\Modules\Commerce;

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

// WP-CLI load tests intentionally exercise custom commerce tables in bulk.
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter

/**
 * WP-CLI load testing helpers for large product catalogs.
 */
final class LoadTestCommand
{
    /**
     * Seed synthetic products for import/search scale testing.
     *
     * ## OPTIONS
     *
     * [--count=<count>]
     * : Number of products to insert.
     * ---
     * default: 1000
     * ---
     *
     * [--batch=<batch>]
     * : Number of rows per insert query.
     * ---
     * default: 500
     * ---
     *
     * [--prefix=<prefix>]
     * : SKU prefix for generated products.
     * ---
     * default: H4LT
     * ---
     *
     * [--variants=<variants>]
     * : Variants to create per product.
     * ---
     * default: 0
     * ---
     *
     * ## EXAMPLES
     *
     *     wp h4bs load seed --count=20000 --batch=1000
     *     wp h4bs load seed --count=150000 --batch=2000 --variants=2
     */
    public function seed(array $args, array $assocArgs): void
    {
        global $wpdb;

        $count = max(1, (int) ($assocArgs['count'] ?? 1000));
        $batch = max(100, min(5000, (int) ($assocArgs['batch'] ?? 500)));
        $prefix = sanitize_key((string) ($assocArgs['prefix'] ?? 'H4LT'));
        $variantsPerProduct = max(0, min(20, (int) ($assocArgs['variants'] ?? 0)));

        $productsTable = $wpdb->prefix . 'h4_products';
        $variantsTable = $wpdb->prefix . 'h4_product_variants';
        $base = gmdate('YmdHis');
        $seedTag = $prefix . '-' . $base;
        $start = microtime(true);
        $inserted = 0;
        $variantInserted = 0;

        for ($offset = 0; $offset < $count; $offset += $batch) {
            $chunk = min($batch, $count - $offset);
            $placeholders = [];
            $params = [];

            for ($i = 0; $i < $chunk; $i++) {
                $seq = $offset + $i + 1;
                $sku = $seedTag . '-' . str_pad((string) $seq, 7, '0', STR_PAD_LEFT);
                $name = 'Load Test Product ' . $seq;
                $slug = sanitize_title($name . '-' . $seq);
                $price = (float) ((($seq % 200) + 1) + 0.99);
                $stock = (int) (($seq % 1000) + 5);
                $payload = wp_json_encode(
                    [
                        'load_test' => true,
                        'seed_tag'  => $seedTag,
                        'seq'       => $seq,
                    ]
                );

                $placeholders[] = '(%s,%s,%s,%s,%f,%s,%d,%s)';
                $params[] = $sku;
                $params[] = $name;
                $params[] = $slug;
                $params[] = 'active';
                $params[] = $price;
                $params[] = 'USD';
                $params[] = $stock;
                $params[] = $payload ?: '{}';
            }

            $ok = $wpdb->query(
                // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- SQL is built from internal table names and generated placeholders only.
                $wpdb->prepare('INSERT INTO %i (sku, name, slug, status, price, currency, stock_qty, payload) VALUES ' . implode(',', $placeholders), ...array_merge([$productsTable], $params))
            );
            if ($ok === false) {
                \WP_CLI::warning('Insert failed near offset ' . $offset . ': ' . (string) $wpdb->last_error);
                continue;
            }

            $inserted += (int) $ok;
            if ($variantsPerProduct > 0) {
                $firstId = (int) $wpdb->insert_id;
                if ($firstId > 0) {
                    $variantInserted += $this->seedVariantsForRange($variantsTable, $firstId, $chunk, $variantsPerProduct);
                }
            }
        }

        $elapsed = max(0.001, microtime(true) - $start);
        $rate = round($inserted / $elapsed, 2);
        \WP_CLI::success(
            sprintf(
                'Seeded %d products (%d variants) in %.2fs (%.2f products/s). seed_tag=%s',
                $inserted,
                $variantInserted,
                $elapsed,
                $rate,
                $seedTag
            )
        );
    }

    /**
     * Benchmark product search/list performance.
     *
     * ## OPTIONS
     *
     * [--search=<search>]
     * : Search term.
     * ---
     * default: load
     * ---
     *
     * [--runs=<runs>]
     * : Number of benchmark iterations.
     * ---
     * default: 20
     * ---
     *
     * [--search_mode=<search_mode>]
     * : Search mode: prefix, contains, exact.
     * ---
     * default: prefix
     * ---
     *
     * [--offset=<offset>]
     * : Offset to benchmark deep pagination.
     * ---
     * default: 0
     * ---
     *
     * [--limit=<limit>]
     * : Rows per benchmark request.
     * ---
     * default: 24
     * ---
     *
     * [--cursor=<cursor>]
     * : Cursor mode (1 to walk pages by cursor instead of offset).
     * ---
     * default: 0
     * ---
     */
    public function benchmark(array $args, array $assocArgs): void
    {
        $search = sanitize_text_field((string) ($assocArgs['search'] ?? 'load'));
        $runs = max(1, min(200, (int) ($assocArgs['runs'] ?? 20)));
        $searchMode = sanitize_key((string) ($assocArgs['search_mode'] ?? 'prefix'));
        if (! in_array($searchMode, ['prefix', 'contains', 'exact'], true)) {
            $searchMode = 'prefix';
        }
        $offset = max(0, (int) ($assocArgs['offset'] ?? 0));
        $limit = max(1, min(200, (int) ($assocArgs['limit'] ?? 24)));
        $repo = new ProductRepository();
        $samples = [];
        $resultCount = 0;
        $cursorUpdatedAt = '';
        $cursorId = 0;
        $cursorMode = ! empty($assocArgs['cursor']);
        $cursorPages = 0;

        for ($i = 0; $i < $runs; $i++) {
            $start = microtime(true);
            $query = [
                'search' => $search,
                'search_mode' => $searchMode,
                'status' => 'active',
                'limit'  => $limit,
                'offset' => $offset,
            ];
            if ($cursorMode && $cursorUpdatedAt !== '' && $cursorId > 0) {
                $query['cursor_updated_at'] = $cursorUpdatedAt;
                $query['cursor_id'] = $cursorId;
            }
            $items = $repo->list($query);
            $ms = (microtime(true) - $start) * 1000;
            $samples[] = $ms;
            $resultCount = count($items);

            if ($cursorMode && $resultCount > 0) {
                $last = end($items);
                if (is_array($last) && ! empty($last['updated_at']) && ! empty($last['id'])) {
                    $cursorUpdatedAt = (string) $last['updated_at'];
                    $cursorId = (int) $last['id'];
                    $cursorPages++;
                } else {
                    $cursorUpdatedAt = '';
                    $cursorId = 0;
                }
            }
        }

        sort($samples);
        $avg = array_sum($samples) / count($samples);
        $p95Index = max(0, (int) floor(count($samples) * 0.95) - 1);
        $p95 = $samples[$p95Index] ?? end($samples);
        $max = max($samples);
        $min = min($samples);

        \WP_CLI::log('Search benchmark results:');
        \WP_CLI::log(
            'runs=' . $runs .
            ' search="' . $search . '"' .
            ' search_mode=' . $searchMode .
            ' limit=' . $limit .
            ' offset=' . $offset .
            ' cursor=' . ($cursorMode ? '1' : '0') .
            ' cursor_pages=' . $cursorPages .
            ' results_per_run=' . $resultCount
        );
        \WP_CLI::log('avg_ms=' . round($avg, 2) . ' p95_ms=' . round((float) $p95, 2) . ' min_ms=' . round((float) $min, 2) . ' max_ms=' . round((float) $max, 2));
    }

    /**
     * Cleanup synthetic load test products/variants by SKU prefix.
     *
     * ## OPTIONS
     *
     * [--prefix=<prefix>]
     * : SKU prefix used for generated test rows.
     * ---
     * default: H4LT
     * ---
     */
    public function cleanup(array $args, array $assocArgs): void
    {
        global $wpdb;

        $prefix = sanitize_key((string) ($assocArgs['prefix'] ?? 'H4LT'));
        $productsTable = $wpdb->prefix . 'h4_products';
        $variantsTable = $wpdb->prefix . 'h4_product_variants';
        $like = $prefix . '-%';

        $productIds = $wpdb->get_col(
            $wpdb->prepare('SELECT id FROM %i WHERE sku LIKE %s', $productsTable, $like)
        );
        if (! is_array($productIds) || ! $productIds) {
            \WP_CLI::success('No seeded products found for prefix ' . $prefix . '.');
            return;
        }

        $ids = array_map('absint', $productIds);
        $ids = array_values(array_filter($ids));
        if (! $ids) {
            \WP_CLI::success('No seeded products found for prefix ' . $prefix . '.');
            return;
        }

        $wpdb->query(
            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Placeholder list is generated from sanitized IDs only.
            $wpdb->prepare('DELETE FROM %i WHERE product_id IN (' . implode(',', array_fill(0, count($ids), '%d')) . ')', ...array_merge([$variantsTable], $ids))
        ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
        $deletedProducts = $wpdb->query(
            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Placeholder list is generated from sanitized IDs only.
            $wpdb->prepare('DELETE FROM %i WHERE id IN (' . implode(',', array_fill(0, count($ids), '%d')) . ')', ...array_merge([$productsTable], $ids))
        ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared

        \WP_CLI::success('Cleanup complete. Deleted products=' . (int) $deletedProducts . ' for prefix=' . $prefix);
    }

    private function seedVariantsForRange(string $variantsTable, int $firstProductId, int $productCount, int $variantsPerProduct): int
    {
        global $wpdb;

        $placeholders = [];
        $params = [];
        $total = 0;
        for ($offset = 0; $offset < $productCount; $offset++) {
            $productId = $firstProductId + $offset;
            for ($v = 1; $v <= $variantsPerProduct; $v++) {
                $sku = 'VAR-' . $productId . '-' . $v;
                $attributes = wp_json_encode(['size' => 'v' . $v]) ?: '{}';
                $price = (float) ($v + 9.99);
                $stock = (int) (100 + $v);
                $placeholders[] = '(%d,%s,%s,%f,%d,%d,%d)';
                $params[] = $productId;
                $params[] = $sku;
                $params[] = $attributes;
                $params[] = $price;
                $params[] = $stock;
                $params[] = 0;
                $params[] = $v;
                $total++;
            }
        }

        if (! $placeholders) {
            return 0;
        }

        $ok = $wpdb->query(
            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- SQL is built from internal table names and generated placeholders only.
            $wpdb->prepare('INSERT INTO %i (product_id, sku, attributes_json, price, stock_qty, image_id, position) VALUES ' . implode(',', $placeholders), ...array_merge([$variantsTable], $params))
        );
        return $ok === false ? 0 : (int) $ok;
    }
}
