<?php
/**
 * @package	HikaShop
 * @version	6.4.0
 * @author	hikashop.com
 * @copyright	(C) 2010-2026 HIKARI SOFTWARE. All rights reserved.
 * @license	GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
 */
defined('_JEXEC') or defined('ABSPATH') or die('Restricted access');
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
?><?php

class UcpJ52Tester {

	private $baseUrl = 'http://localhost/j52';
	private $apiKey = 'your-api-key-here';
	private $verbose = false;

	private $sessionId = null;
	private $lastResponse = null;
	private $lastRawResponse = '';
	private $lastHttpCode = 0;

	private $passed = 0;
	private $failed = 0;
	private $warnings = 0;

	const GREEN  = "\033[32m";
	const RED    = "\033[31m";
	const YELLOW = "\033[33m";
	const CYAN   = "\033[36m";
	const RESET  = "\033[0m";

	public function __construct($verbose = false) {
		$this->verbose = $verbose;
	}


	public function runAll() {
		$this->header('UCP Plugin Test Suite - J52');
		$this->info("Base URL: {$this->baseUrl}");
		$this->info("API Key: " . substr($this->apiKey, 0, 8) . "...");
		echo "\n";

		$this->testDiscovery();
		$this->testAuth();
		$this->testSearch();
		$this->testSearchCategory();
		$this->testSearchPagination();
		$this->testSearchSort();
		$this->testSearchOutOfStock();
		$this->testPaymentMethods();
		$this->testErrors();
		$this->testFullFlow();
		$this->testCancelFlow();

		echo "\n";
		$this->header('Results');
		$total = $this->passed + $this->failed;
		echo self::GREEN  . "  Passed:   {$this->passed}" . self::RESET . "\n";
		if($this->failed > 0)
			echo self::RED . "  Failed:   {$this->failed}" . self::RESET . "\n";
		else
			echo "  Failed:   0\n";
		if($this->warnings > 0)
			echo self::YELLOW . "  Warnings: {$this->warnings}" . self::RESET . "\n";
		echo "  Total:    {$total}\n";

		return $this->failed === 0;
	}


	public function testDiscovery() {
		$this->header('1. Discovery (/.well-known/ucp)');

		$r = $this->get('/.well-known/ucp', false);

		$this->assert('Discovery returns HTTP 200', $this->lastHttpCode === 200);
		if($this->lastHttpCode !== 200) { $this->showError($r); return; }

		$this->assert('Response has ucp field', !empty($r['ucp']));
		$this->assert('UCP version is present', !empty($r['ucp']['version']));
		$this->info("  Version: " . ($r['ucp']['version'] ?? '?'));

		$this->assert('Response has services', !empty($r['ucp']['services']));
		if(!empty($r['ucp']['services'])) {
			$svc = reset($r['ucp']['services']);
			$this->assert('Service has REST endpoint', !empty($svc['rest']['endpoint']));
			$this->info("  REST endpoint: " . ($svc['rest']['endpoint'] ?? '?'));
			if(!empty($svc['mcp'])) {
				$this->info("  MCP endpoint: " . ($svc['mcp']['endpoint'] ?? '?'));
			}
		}

		$this->assert('Response has capabilities', !empty($r['ucp']['capabilities']));
		if(!empty($r['ucp']['capabilities'])) {
			$names = array_column($r['ucp']['capabilities'], 'name');
			$this->info("  Capabilities: " . implode(', ', $names));
			$this->assert('Has catalog capability', in_array('dev.ucp.shopping.catalog', $names));
			$this->assert('Has checkout capability', in_array('dev.ucp.shopping.checkout', $names));
		}
	}


	public function testAuth() {
		$this->header('2. Authentication');

		$r = $this->post('/ucp/v1/search', ['limit' => 1]);
		$this->assert('Valid API key: HTTP 200', $this->lastHttpCode === 200);

		$r = $this->post('/ucp/v1/search', ['limit' => 1], false);
		$this->assert('Missing API key: HTTP 403', $this->lastHttpCode === 403);

		$r = $this->request('POST', '/ucp/v1/search', ['limit' => 1], 'wrong-key-12345');
		$this->assert('Wrong API key: HTTP 403', $this->lastHttpCode === 403);

		$r = $this->get('/.well-known/ucp', false);
		$this->assert('Discovery without key: HTTP 200', $this->lastHttpCode === 200);
	}


	public function testSearch() {
		$this->header('3. Search (basic)');

		$r = $this->post('/ucp/v1/search', ['limit' => 5]);
		$this->assert('Search returns HTTP 200', $this->lastHttpCode === 200);
		if($this->lastHttpCode !== 200) { $this->showError($r); return; }

		$this->assert('Response has products array', isset($r['products']));
		$this->assert('Response has total count', isset($r['total']));
		$this->assert('Response has limit', isset($r['limit']));
		$this->assert('Response has offset', isset($r['offset']));
		$this->assert('Products returned', count($r['products']) > 0);

		$this->info("  Total: {$r['total']}, Returned: " . count($r['products']));

		if(!empty($r['products'])) {
			$p = $r['products'][0];
			$this->assert('Product has id', !empty($p['id']));
			$this->assert('Product id is global ID', strpos($p['id'], 'gid://') === 0);
			$this->assert('Product has name', !empty($p['name']));
			$this->assert('Product has sku', isset($p['sku']));
			$this->assert('Product has availability', !empty($p['availability']));
			$this->assert('Product has price', !empty($p['price']));
			$this->assert('Price has amount (cents)', isset($p['price']['amount']));
			$this->assert('Price has currency', !empty($p['price']['currency']));
			$this->assert('Price amount is integer', is_int($p['price']['amount']));
			$this->info("  First product: [{$p['id']}] {$p['name']} - {$p['price']['amount']} {$p['price']['currency']}");
		}

		$r = $this->post('/ucp/v1/search', ['query' => 'Porsche', 'limit' => 5]);
		$this->assert('Text search returns HTTP 200', $this->lastHttpCode === 200);
		if($this->lastHttpCode === 200 && is_array($r) && isset($r['products'])) {
			$this->assert('Text search finds results', count($r['products']) > 0);
			$this->info("  Query 'Porsche': {$r['total']} result(s)");
			if(!empty($r['products'])) {
				$firstMatch = $r['products'][0]['name'];
				$this->assert('First result contains search term', stripos($firstMatch, 'Porsche') !== false);
			}
		}

		$r = $this->post('/ucp/v1/search', ['query' => 'xyznonexistent99', 'limit' => 5]);
		$this->assert('Empty search returns HTTP 200', $this->lastHttpCode === 200);
		if($this->lastHttpCode === 200 && is_array($r) && isset($r['products'])) {
			$this->assert('Empty search returns 0 products', count($r['products']) === 0);
			$this->assert('Empty search total is 0', ($r['total'] ?? -1) === 0);
		}
	}


	public function testSearchCategory() {
		$this->header('4. Search (category filter)');

		$r = $this->post('/ucp/v1/search', ['category' => '12', 'limit' => 5]);
		$this->assert('Category search returns HTTP 200', $this->lastHttpCode === 200);
		if($this->lastHttpCode === 200) {
			$this->info("  Category 12 (Shoes): {$r['total']} product(s)");
			if(count($r['products']) === 0)
				$this->warn('No products directly in category 12 (all in subcategories) - use include_subcategories=true');
			else
				$this->assert('Category search finds products', true);
		}

		$r = $this->post('/ucp/v1/search', ['category' => '12', 'include_subcategories' => true, 'limit' => 50]);
		$withSub = $r['total'] ?? 0;
		$r2 = $this->post('/ucp/v1/search', ['category' => '12', 'include_subcategories' => false, 'limit' => 50]);
		$withoutSub = $r2['total'] ?? 0;
		$this->info("  With subcategories: $withSub, Without: $withoutSub");
		$this->assert('include_subcategories returns >= without', $withSub >= $withoutSub);
	}


	public function testSearchPagination() {
		$this->header('5. Search (pagination)');

		$r = $this->post('/ucp/v1/search', ['limit' => 2, 'offset' => 0]);
		$this->assert('Pagination page 1: HTTP 200', $this->lastHttpCode === 200);
		if($this->lastHttpCode !== 200) return;

		$total = $r['total'] ?? 0;
		$page1Ids = array_column($r['products'], 'id');
		$this->assert('Limit respected', count($r['products']) <= 2);
		$this->assert('Offset is 0', $r['offset'] === 0);
		$this->info("  Page 1: " . count($r['products']) . " products, total: $total");

		if($total > 2) {
			$r = $this->post('/ucp/v1/search', ['limit' => 2, 'offset' => 2]);
			$this->assert('Pagination page 2: HTTP 200', $this->lastHttpCode === 200);
			$this->assert('Offset is 2', $r['offset'] === 2);
			$page2Ids = array_column($r['products'], 'id');
			$this->assert('Page 2 has different products', empty(array_intersect($page1Ids, $page2Ids)));
			$this->info("  Page 2: " . count($r['products']) . " products");
		}
	}


	public function testSearchSort() {
		$this->header('6. Search (sorting)');

		$r = $this->post('/ucp/v1/search', ['sort' => 'price', 'sort_direction' => 'asc', 'limit' => 5]);
		$this->assert('Sort by price asc: HTTP 200', $this->lastHttpCode === 200);
		if($this->lastHttpCode === 200 && count($r['products']) >= 2) {
			$prices = array_map(function($p) { return $p['price']['amount'] ?? 0; }, $r['products']);
			$sorted = $prices;
			sort($sorted);
			if($prices !== $sorted)
				$this->warn('Prices not ascending - product_sort_price may need recalculation (run HikaShop product price recalculation)');
			else
				$this->assert('Prices are ascending', true);
			$this->info("  Prices: " . implode(', ', $prices));
		}

		$r = $this->post('/ucp/v1/search', ['sort' => 'price', 'sort_direction' => 'desc', 'limit' => 5]);
		$this->assert('Sort by price desc: HTTP 200', $this->lastHttpCode === 200);
		if($this->lastHttpCode === 200 && count($r['products']) >= 2) {
			$prices = array_map(function($p) { return $p['price']['amount'] ?? 0; }, $r['products']);
			$sortedDesc = $prices;
			rsort($sortedDesc);
			if($prices !== $sortedDesc)
				$this->warn('Prices not descending - product_sort_price may need recalculation');
			else
				$this->assert('Prices are descending', true);
		}

		$r = $this->post('/ucp/v1/search', ['sort' => 'name', 'sort_direction' => 'asc', 'limit' => 5]);
		$this->assert('Sort by name: HTTP 200', $this->lastHttpCode === 200);
		if($this->lastHttpCode === 200 && count($r['products']) >= 2) {
			$names = array_column($r['products'], 'name');
			$sorted = $names;
			sort($sorted, SORT_NATURAL | SORT_FLAG_CASE);
			if($names !== $sorted)
				$this->warn('Names not ascending - variant subnames may affect sort order');
			else
				$this->assert('Names are ascending', true);
			$this->info("  First: {$names[0]}");
		}
	}


	public function testSearchOutOfStock() {
		$this->header('7. Search (out of stock filter)');

		$r = $this->post('/ucp/v1/search', ['include_out_of_stock' => false, 'limit' => 50]);
		$this->assert('Out of stock filter: HTTP 200', $this->lastHttpCode === 200);
		if($this->lastHttpCode === 200) {
			$outOfStock = array_filter($r['products'], function($p) {
				return ($p['availability'] ?? '') === 'out_of_stock';
			});
			$this->assert('No out_of_stock products returned', count($outOfStock) === 0);
			$this->info("  Returned: " . count($r['products']) . " in-stock products");
		}
	}


	public function testPaymentMethods() {
		$this->header('8. Payment Methods');

		$r = $this->get('/ucp/v1/payment-methods');
		$this->assert('Payment methods returns HTTP 200', $this->lastHttpCode === 200);
		if($this->lastHttpCode !== 200) { $this->showError($r); return; }

		$this->assert('Response has payment_methods array', isset($r['payment_methods']));
		$count = count($r['payment_methods'] ?? []);
		$this->info("  Available methods (with getPaymentURL): $count");

		foreach($r['payment_methods'] as $m) {
			$this->info("  [{$m['id']}] {$m['name']} ({$m['type']})");
		}

		if($count === 0) {
			$this->warn('No payment methods support getPaymentURL - complete with hosted URL will not work');
		}
	}


	public function testErrors() {
		$this->header('9. Error Handling');

		$r = $this->post('/ucp/v1/checkout-sessions', []);
		$this->assert('Create without line_items: HTTP 400', $this->lastHttpCode === 400);

		$r = $this->post('/ucp/v1/checkout-sessions', ['line_items' => []]);
		$this->assert('Create with empty line_items: HTTP 400', $this->lastHttpCode === 400);

		$r = $this->post('/ucp/v1/checkout-sessions', [
			'line_items' => [['item' => ['id' => '999999'], 'quantity' => 1]]
		]);
		$this->assert('Create with invalid product: HTTP 400', $this->lastHttpCode === 400);

		$r = $this->get('/ucp/v1/checkout-sessions/999999');
		$this->assert('Get non-existent session: HTTP 404', $this->lastHttpCode === 404);

		$r = $this->put('/ucp/v1/checkout-sessions/999999', ['line_items' => []]);
		$this->assert('Update non-existent session: HTTP 404', $this->lastHttpCode === 404);

		$r = $this->post('/ucp/v1/checkout-sessions/999999/cancel', []);
		$this->assert('Cancel non-existent session: HTTP 404', $this->lastHttpCode === 404);

		$r = $this->post('/ucp/v1/search', ['limit' => -1]);
		$this->assert('Search with invalid limit: HTTP 200 or 400', $this->lastHttpCode === 200 || $this->lastHttpCode === 400);

		$r = $this->get('/ucp/v1/nonexistent');
		$this->assert('Non-existent endpoint: HTTP 404', $this->lastHttpCode === 404);

		$r = $this->get('/ucp/v1/orders/1');
		$this->assert('Orders endpoint: HTTP 501', $this->lastHttpCode === 501);
	}


	public function testFullFlow() {
		$this->header('10. Full Checkout Flow');

		$this->info('Step 1: Create checkout session');
		$createData = [
			'line_items' => [
				['item' => ['id' => '9'], 'quantity' => 2],
				['item' => ['id' => '10'], 'quantity' => 1],
			],
			'buyer' => [
				'first_name' => 'John',
				'last_name' => 'Doe',
				'email' => 'john.doe@example.com',
			],
			'fulfillment' => [
				'methods' => [[
					'type' => 'shipping',
					'destinations' => [[
						'id' => 'dest_1',
						'street_address' => '123 Main Street',
						'address_locality' => 'Paris',
						'postal_code' => '75001',
						'country' => 'France',
						'address_region' => 'Ile-de-France',
					]],
					'selected_destination_id' => 'dest_1',
				]]
			]
		];

		$r = $this->post('/ucp/v1/checkout-sessions', $createData);
		$this->assert('Create: HTTP 201', $this->lastHttpCode === 201);
		if($this->lastHttpCode !== 201) { $this->showError($r); return; }

		$this->assert('Create: has id', !empty($r['id']));
		$this->assert('Create: status is incomplete', ($r['status'] ?? '') === 'incomplete');
		$this->assert('Create: has currency', !empty($r['currency']));
		$this->assert('Create: has line_items', !empty($r['line_items']));
		$this->assert('Create: 2 line items', count($r['line_items']) === 2);
		$this->assert('Create: has totals', !empty($r['totals']));

		$this->sessionId = $r['id'];
		$localId = $this->extractLocalId($this->sessionId);
		$this->info("  Session: {$this->sessionId}");
		$this->info("  Currency: {$r['currency']}");

		foreach($r['line_items'] as $li) {
			$price = number_format(($li['unit_price']['amount'] ?? 0) / 100, 2);
			$this->info("  Item: {$li['item']['title']} x{$li['quantity']} @ {$price} {$li['unit_price']['currency']}");
		}

		$this->printTotals($r);

		$this->assert('Create: buyer returned', !empty($r['buyer']));
		if(!empty($r['buyer'])) {
			$this->assert('Create: buyer first_name', ($r['buyer']['first_name'] ?? '') === 'John');
			$this->assert('Create: buyer last_name', ($r['buyer']['last_name'] ?? '') === 'Doe');
			$this->assert('Create: buyer email', ($r['buyer']['email'] ?? '') === 'john.doe@example.com');
		}

		$shippingOptions = $r['fulfillment']['methods'][0]['options'] ?? [];
		$this->info("  Shipping options: " . count($shippingOptions));
		foreach($shippingOptions as $opt) {
			$price = number_format(($opt['amount'] ?? 0) / 100, 2);
			$this->info("    [{$opt['id']}] {$opt['name']}: {$price}");
		}

		echo "\n";
		$this->info('Step 2: Get checkout session');
		$r = $this->get("/ucp/v1/checkout-sessions/{$localId}");
		$this->assert('Get: HTTP 200', $this->lastHttpCode === 200);
		$this->assert('Get: same session id', ($r['id'] ?? '') === $this->sessionId);
		$this->assert('Get: status is incomplete', ($r['status'] ?? '') === 'incomplete');

		if(isset($r['available_payment_methods'])) {
			$this->info("  Available payment methods: " . count($r['available_payment_methods']));
		}

		if(!empty($shippingOptions)) {
			echo "\n";
			$this->info('Step 3: Select shipping option');
			$shippingId = $shippingOptions[0]['id'];
			$r = $this->put("/ucp/v1/checkout-sessions/{$localId}", [
				'fulfillment' => [
					'methods' => [[
						'type' => 'shipping',
						'selected_option_id' => $shippingId,
					]]
				]
			]);
			$this->assert('Update shipping: HTTP 200', $this->lastHttpCode === 200);
			if($this->lastHttpCode === 200) {
				$this->info("  Selected shipping: {$shippingId}");
				$this->printTotals($r);

				$shippingTotal = $this->findTotal($r, 'shipping');
				if($shippingTotal !== null) {
					$this->assert('Shipping total > 0', $shippingTotal > 0);
				}
			}
		}

		echo "\n";
		$this->info('Step 4: Update line items (change quantities)');
		$r = $this->put("/ucp/v1/checkout-sessions/{$localId}", [
			'line_items' => [
				['item' => ['id' => '9'], 'quantity' => 1],
			]
		]);
		$this->assert('Update items: HTTP 200', $this->lastHttpCode === 200);
		if($this->lastHttpCode === 200) {
			$this->assert('Update items: 1 line item now', count($r['line_items'] ?? []) === 1);
			$this->printTotals($r);
		}

		echo "\n";
		$this->info('Step 5: Update buyer info');
		$r = $this->put("/ucp/v1/checkout-sessions/{$localId}", [
			'buyer' => [
				'first_name' => 'Jane',
				'last_name' => 'Smith',
				'email' => 'jane.smith@example.com',
			]
		]);
		$this->assert('Update buyer: HTTP 200', $this->lastHttpCode === 200);
		if($this->lastHttpCode === 200) {
			$this->assert('Buyer updated: first_name', ($r['buyer']['first_name'] ?? '') === 'Jane');
			$this->assert('Buyer updated: last_name', ($r['buyer']['last_name'] ?? '') === 'Smith');
			$this->assert('Buyer updated: email', ($r['buyer']['email'] ?? '') === 'jane.smith@example.com');
		}

		echo "\n";
		$this->info('Step 6a: Complete without payment info (security check)');
		$r = $this->post("/ucp/v1/checkout-sessions/{$localId}/complete", [
			'payment_data' => [
				'handler_id' => 'test',
				'note' => 'Test order from test_on_j52.php',
			]
		]);
		$this->assert('Complete without credential: HTTP 400', $this->lastHttpCode === 400);

		echo "\n";
		$this->info('Step 6b: Complete checkout with credential');
		$r = $this->post("/ucp/v1/checkout-sessions/{$localId}/complete", [
			'payment_data' => [
				'handler_id' => 'test',
				'credential' => ['type' => 'test', 'token' => 'test_token_' . time()],
				'note' => 'Test order from test_on_j52.php',
			]
		]);
		$this->assert('Complete: HTTP 200 or 402', $this->lastHttpCode === 200 || $this->lastHttpCode === 402);
		if($this->lastHttpCode === 402) {
			$this->info("  Payment failed as expected (no plugin handles test credential)");
			$this->assert('Complete 402: has error message', !empty($r['messages']));
		} elseif($this->lastHttpCode === 200) {
			$status = $r['status'] ?? 'unknown';
			$this->info("  Status: $status");
			$this->assert('Complete: status is completed', $status === 'completed');

			if(!empty($r['order'])) {
				$this->assert('Complete: order has id', !empty($r['order']['id']));
				$this->info("  Order: {$r['order']['id']}");
				if(!empty($r['order']['permalink_url'])) {
					$this->info("  Order URL: {$r['order']['permalink_url']}");
				}
			} else {
				$this->warn('No order info in complete response');
			}
		} else {
			$this->showError($r);
		}

		echo "\n";
		if($this->lastHttpCode === 200) {
			$this->info('Step 7: Verify completed session cannot be modified');
			$r = $this->put("/ucp/v1/checkout-sessions/{$localId}", [
				'line_items' => [['item' => ['id' => '9'], 'quantity' => 5]]
			]);
			$this->assert('Update completed session: not HTTP 200', $this->lastHttpCode !== 200);
			$this->info("  HTTP {$this->lastHttpCode} (expected non-200)");
		} else {
			$this->info('Step 7: Verify session is still incomplete after payment failure');
			$r = $this->get("/ucp/v1/checkout-sessions/{$localId}");
			$this->assert('Session still accessible after payment failure', $this->lastHttpCode === 200);
			$this->assert('Session status is incomplete', ($r['status'] ?? '') === 'incomplete');
			$this->info("  Status: " . ($r['status'] ?? 'unknown'));
		}
	}


	public function testCancelFlow() {
		$this->header('11. Cancel Flow');

		$r = $this->post('/ucp/v1/checkout-sessions', [
			'line_items' => [
				['item' => ['id' => '9'], 'quantity' => 1],
			]
		]);
		$this->assert('Create for cancel: HTTP 201', $this->lastHttpCode === 201);
		if($this->lastHttpCode !== 201) { $this->showError($r); return; }

		$cancelId = $this->extractLocalId($r['id']);
		$this->info("  Session: {$r['id']}");

		$r = $this->post("/ucp/v1/checkout-sessions/{$cancelId}/cancel", []);
		$this->assert('Cancel: HTTP 200', $this->lastHttpCode === 200);
		$this->assert('Cancel: status is canceled', ($r['status'] ?? '') === 'canceled');

		$r = $this->put("/ucp/v1/checkout-sessions/{$cancelId}", [
			'line_items' => [['item' => ['id' => '9'], 'quantity' => 5]]
		]);
		$this->assert('Update canceled session: not HTTP 200', $this->lastHttpCode !== 200);
		$this->info("  HTTP {$this->lastHttpCode} (expected non-200)");

		$r = $this->post("/ucp/v1/checkout-sessions/{$cancelId}/complete", []);
		$this->assert('Complete canceled session: not HTTP 200', $this->lastHttpCode !== 200);
		$this->info("  HTTP {$this->lastHttpCode} (expected non-200)");
	}


	private function get($path, $auth = true) {
		return $this->request('GET', $path, null, $auth ? $this->apiKey : null);
	}

	private function post($path, $data, $auth = true) {
		return $this->request('POST', $path, $data, $auth ? $this->apiKey : null);
	}

	private function put($path, $data, $auth = true) {
		return $this->request('PUT', $path, $data, $auth ? $this->apiKey : null);
	}

	private function request($method, $path, $data = null, $apiKey = null) {
		$url = $this->baseUrl . $path;

		$ch = curl_init();
		curl_setopt($ch, CURLOPT_URL, $url);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
		curl_setopt($ch, CURLOPT_TIMEOUT, 30);

		$headers = [
			'Content-Type: application/json',
			'Accept: application/json',
		];

		if($apiKey !== null) {
			$headers[] = 'X-API-KEY: ' . $apiKey;
		}

		curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

		switch($method) {
			case 'POST':
				curl_setopt($ch, CURLOPT_POST, true);
				if($data !== null)
					curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
				break;
			case 'PUT':
				curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
				if($data !== null)
					curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
				break;
			case 'DELETE':
				curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
				break;
		}

		if($this->verbose) {
			$this->debug("$method $url");
			if($data !== null)
				$this->debug("Body: " . json_encode($data, JSON_PRETTY_PRINT));
		}

		$response = curl_exec($ch);
		$this->lastHttpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
		$this->lastRawResponse = $response;

		if(curl_errno($ch)) {
			$this->fail("CURL Error: " . curl_error($ch));
			curl_close($ch);
			return null;
		}

		curl_close($ch);

		$decoded = json_decode($response, true);
		$this->lastResponse = $decoded;

		if($this->verbose && $decoded !== null) {
			$this->debug("Response [{$this->lastHttpCode}]: " . json_encode($decoded, JSON_PRETTY_PRINT));
		} elseif($this->verbose && $response) {
			$this->debug("Response [{$this->lastHttpCode}]: " . substr($response, 0, 500));
		}

		return $decoded;
	}


	private function assert($label, $condition) {
		if($condition) {
			$this->passed++;
			echo self::GREEN . "  [PASS] " . self::RESET . "$label\n";
		} else {
			$this->failed++;
			echo self::RED . "  [FAIL] " . self::RESET . "$label\n";
		}
	}

	private function extractLocalId($globalId) {
		if(strpos($globalId, 'gid://') === 0) {
			$parts = explode('/', $globalId);
			return end($parts);
		}
		return $globalId;
	}

	private function findTotal($response, $type) {
		foreach($response['totals'] ?? [] as $t) {
			if(($t['type'] ?? '') === $type)
				return $t['amount'] ?? null;
		}
		return null;
	}

	private function printTotals($response) {
		if(empty($response['totals'])) return;
		$this->info('  Totals:');
		foreach($response['totals'] as $t) {
			$amount = number_format(($t['amount'] ?? 0) / 100, 2);
			$this->info("    {$t['type']}: {$amount}");
		}
	}

	private function header($text) {
		echo self::CYAN . "\n=== $text ===" . self::RESET . "\n";
	}

	private function info($text) {
		echo "  [INFO] $text\n";
	}

	private function pass($text) {
		echo self::GREEN . "  [PASS] " . self::RESET . "$text\n";
	}

	private function fail($text) {
		echo self::RED . "  [FAIL] " . self::RESET . "$text\n";
	}

	private function warn($text) {
		$this->warnings++;
		echo self::YELLOW . "  [WARN] " . self::RESET . "$text\n";
	}

	private function debug($text) {
		echo self::YELLOW . "  [DEBUG] " . self::RESET . "$text\n";
	}

	private function showError($response) {
		if(!empty($response['messages'])) {
			foreach($response['messages'] as $msg) {
				$this->fail("  Server: " . ($msg['message'] ?? 'Unknown error'));
			}
		} elseif(!empty($response['error'])) {
			$this->fail("  Server: " . $response['error']);
		} elseif($this->lastRawResponse) {
			$this->fail("  Raw: " . substr($this->lastRawResponse, 0, 300));
		}
	}
}


$options = getopt('', ['test:', 'verbose', 'help']);

if(isset($options['help'])) {
	echo <<<HELP
UCP Plugin Test Script for J52

Usage:
  php test_on_j52.php [options]

Options:
  --test=name   Run a single test. Available:
                discovery, auth, search, search_category, search_pagination,
                search_sort, search_out_of_stock, payment_methods,
                errors, full_flow, cancel
  --verbose     Show full request/response bodies
  --help        Show this help

HELP;
	exit(0);
}

$verbose = isset($options['verbose']);
$tester = new UcpJ52Tester($verbose);

$testName = $options['test'] ?? '';
if(!empty($testName)) {
	$map = [
		'discovery'        => 'testDiscovery',
		'auth'             => 'testAuth',
		'search'           => 'testSearch',
		'search_category'  => 'testSearchCategory',
		'search_pagination'=> 'testSearchPagination',
		'search_sort'      => 'testSearchSort',
		'search_out_of_stock' => 'testSearchOutOfStock',
		'payment_methods'  => 'testPaymentMethods',
		'errors'           => 'testErrors',
		'full_flow'        => 'testFullFlow',
		'cancel'           => 'testCancelFlow',
	];

	if(!isset($map[$testName])) {
		echo "Unknown test: $testName\nAvailable: " . implode(', ', array_keys($map)) . "\n";
		exit(1);
	}

	$method = $map[$testName];
	$tester->$method();
} else {
	$success = $tester->runAll();
	exit($success ? 0 : 1);
}
