<?php
/**
 * Catalogue diagnostics.
 *
 * This exists to answer, with real numbers rather than guesses, the questions
 * that were open when the integration was quoted:
 *
 *   1. How big is the catalogue, and how much of it is flagged sellable online?
 *   2. What share of `pid` values are genuine GTINs? That decides how well
 *      Google Shopping can work, because an invalid GTIN submitted as a GTIN
 *      gets the product disapproved outright.
 *   3. What share of products actually have an image? Merchant Center requires
 *      one, and a storefront needs one to look like a shop.
 *   4. Which descriptive fields are populated well enough to build filtering on.
 *
 * The scan runs in AJAX batches. A full catalogue is many pages and there is no
 * documented rate limit, so batching keeps us inside PHP's execution limit and
 * lets a human watch the request rate rather than firing hundreds of calls blind.
 */

defined( 'ABSPATH' ) || exit;

class BW_Barnet_Diagnostics {

	const PAGE      = 'bw-barnet-diagnostics';
	const NONCE     = 'bw_barnet_scan';
	const STATE_KEY = 'bw_barnet_scan_state';
	const PER_PAGE  = 100;

	/** Fields whose population rate we report on. */
	private const TRACKED_FIELDS = array(
		'description',
		'more_info_html',
		'image',
		'category_name',
		'group_name',
		'producer_name',
		'vendor_name',
		'distributor_name',
		'country_name',
		'varital_name',
		'alcohol',
		'sweetness',
		'on_hand',
		'sale_price',
	);

	public static function init(): void {
		add_action( 'admin_menu', array( __CLASS__, 'menu' ), 20 );
		add_action( 'wp_ajax_bw_barnet_scan_batch', array( __CLASS__, 'ajax_scan_batch' ) );
		add_action( 'wp_ajax_bw_barnet_scan_reset', array( __CLASS__, 'ajax_scan_reset' ) );
	}

	public static function menu(): void {
		add_submenu_page(
			BW_Barnet_Settings::PAGE,
			__( 'Catalogue Diagnostics', 'bw-barnet' ),
			__( 'Diagnostics', 'bw-barnet' ),
			BW_BARNET_CAP,
			self::PAGE,
			array( __CLASS__, 'render' )
		);
	}

	private static function blank_state(): array {
		$fields = array();
		foreach ( self::TRACKED_FIELDS as $f ) {
			$fields[ $f ] = 0;
		}

		return array(
			'scanned'      => 0,
			'total'        => null,
			'pages'        => null,
			'next_page'    => 1,
			'show_on_web'  => 0,
			'gtin'         => array( 'valid' => 0, 'repaired' => 0, 'invalid' => 0, 'empty' => 0 ),
			'doubled_sku'  => 0,
			'fields'       => $fields,
			'examples'     => array( 'invalid' => array(), 'repaired' => array() ),
			'started'      => time(),
			'complete'     => false,
		);
	}

	public static function ajax_scan_reset(): void {
		if ( ! current_user_can( BW_BARNET_CAP ) ) {
			wp_send_json_error( array( 'message' => 'forbidden' ), 403 );
		}
		check_ajax_referer( self::NONCE );
		delete_transient( self::STATE_KEY );
		wp_send_json_success( self::blank_state() );
	}

	public static function ajax_scan_batch(): void {
		if ( ! current_user_can( BW_BARNET_CAP ) ) {
			wp_send_json_error( array( 'message' => 'forbidden' ), 403 );
		}
		check_ajax_referer( self::NONCE );

		$state = get_transient( self::STATE_KEY );
		if ( ! is_array( $state ) ) {
			$state = self::blank_state();
		}

		$web_only = ! empty( $_POST['web_only'] );

		$response = BW_Barnet_Client::get_products(
			(int) $state['next_page'],
			self::PER_PAGE,
			$web_only ? true : null
		);

		if ( is_wp_error( $response ) ) {
			wp_send_json_error( array( 'message' => $response->get_error_message() ) );
		}

		$paginator = $response['paginator'] ?? array();
		if ( isset( $paginator['items_count'] ) ) {
			$state['total'] = (int) $paginator['items_count'];
		}
		if ( isset( $paginator['pages'] ) ) {
			$state['pages'] = (int) $paginator['pages'];
		}

		$items = $response['items'] ?? array();
		if ( ! is_array( $items ) ) {
			$items = array();
		}

		foreach ( $items as $item ) {
			if ( ! is_array( $item ) ) {
				continue;
			}
			$state['scanned']++;

			if ( ! empty( $item['show_on_web'] ) ) {
				$state['show_on_web']++;
			}

			$verdict = BW_Barnet_GTIN::evaluate( $item['pid'] ?? '' );
			$state['gtin'][ $verdict['status'] ]++;

			if ( BW_Barnet_GTIN::looks_like_doubled_sku( $item['pid'] ?? '', $item['cspcid'] ?? '' ) ) {
				$state['doubled_sku']++;
			}

			if ( BW_Barnet_GTIN::STATUS_INVALID === $verdict['status'] && count( $state['examples']['invalid'] ) < 8 ) {
				$state['examples']['invalid'][] = array(
					'cspcid' => (string) ( $item['cspcid'] ?? '' ),
					'pid'    => (string) ( $item['pid'] ?? '' ),
					'name'   => (string) ( $item['description'] ?? '' ),
					'reason' => $verdict['reason'],
				);
			}
			if ( BW_Barnet_GTIN::STATUS_REPAIRED === $verdict['status'] && count( $state['examples']['repaired'] ) < 5 ) {
				$state['examples']['repaired'][] = array(
					'cspcid' => (string) ( $item['cspcid'] ?? '' ),
					'pid'    => (string) ( $item['pid'] ?? '' ),
					'gtin'   => $verdict['gtin'],
					'name'   => (string) ( $item['description'] ?? '' ),
				);
			}

			foreach ( self::TRACKED_FIELDS as $field ) {
				$value = $item[ $field ] ?? null;
				if ( null !== $value && '' !== $value && '0' !== $value && 'null' !== $value ) {
					$state['fields'][ $field ]++;
				}
			}
		}

		$state['next_page']++;
		$state['complete'] = empty( $items )
			|| ( null !== $state['pages'] && $state['next_page'] > $state['pages'] );

		set_transient( self::STATE_KEY, $state, HOUR_IN_SECONDS * 6 );

		wp_send_json_success( $state );
	}

	public static function render(): void {
		if ( ! current_user_can( BW_BARNET_CAP ) ) {
			return;
		}
		$state = get_transient( self::STATE_KEY );
		$state = is_array( $state ) ? $state : self::blank_state();
		$ready = BW_Barnet_Client::has_credentials();
		?>
		<div class="wrap">
			<h1><?php esc_html_e( 'Catalogue Diagnostics', 'bw-barnet' ); ?></h1>
			<p class="description" style="max-width:52em">
				<?php esc_html_e( 'Walks the Barnet catalogue and reports what the data actually contains. This answers how viable Google Shopping is, how complete the product imagery is, and which fields are populated well enough to build category filtering on.', 'bw-barnet' ); ?>
			</p>

			<?php if ( ! $ready ) : ?>
				<div class="notice notice-warning"><p>
					<?php
					printf(
						/* translators: %s: link to settings screen */
						esc_html__( 'Add API credentials on the %s screen first.', 'bw-barnet' ),
						'<a href="' . esc_url( admin_url( 'admin.php?page=' . BW_Barnet_Settings::PAGE ) ) . '">' . esc_html__( 'Settings', 'bw-barnet' ) . '</a>'
					);
					?>
				</p></div>
			<?php else : ?>

			<p>
				<label><input type="checkbox" id="bw-web-only" checked> <?php esc_html_e( 'Only items flagged sellable online (show_on_web)', 'bw-barnet' ); ?></label>
			</p>
			<p>
				<button class="button button-primary" id="bw-scan-start"><?php esc_html_e( 'Run scan', 'bw-barnet' ); ?></button>
				<button class="button" id="bw-scan-reset"><?php esc_html_e( 'Reset', 'bw-barnet' ); ?></button>
				<span id="bw-scan-status" style="margin-left:12px;font-weight:600"></span>
			</p>
			<div id="bw-scan-progress" style="display:none;background:#e0e0e0;height:20px;max-width:600px;border-radius:3px;overflow:hidden">
				<div id="bw-scan-bar" style="background:#2271b1;height:100%;width:0;transition:width .2s"></div>
			</div>

			<div id="bw-scan-report" style="margin-top:24px"></div>

			<script>
			( function () {
				const ajaxUrl = <?php echo wp_json_encode( admin_url( 'admin-ajax.php' ) ); ?>;
				const nonce   = <?php echo wp_json_encode( wp_create_nonce( self::NONCE ) ); ?>;
				let running = false;

				const el = id => document.getElementById( id );
				const pct = ( n, d ) => d > 0 ? ( n / d * 100 ) : 0;
				const fmt = n => ( n === null || n === undefined ) ? '?' : n.toLocaleString();

				function bar( label, count, total, tone ) {
					const p = pct( count, total );
					const colour = tone === 'good' ? '#00a32a' : tone === 'warn' ? '#dba617' : tone === 'bad' ? '#d63638' : '#2271b1';
					return `<tr>
						<td style="padding:4px 12px 4px 0;white-space:nowrap">${label}</td>
						<td style="padding:4px 0;width:100%">
							<div style="background:#f0f0f1;height:16px;border-radius:2px;overflow:hidden;min-width:220px">
								<div style="background:${colour};height:100%;width:${p.toFixed(1)}%"></div>
							</div>
						</td>
						<td style="padding:4px 0 4px 12px;white-space:nowrap;text-align:right">
							<strong>${p.toFixed(1)}%</strong> <span style="color:#666">(${fmt(count)})</span>
						</td>
					</tr>`;
				}

				function report( s ) {
					const n = s.scanned || 0;
					if ( ! n ) { el('bw-scan-report').innerHTML = ''; return; }

					const g = s.gtin || {};
					const usable = ( g.valid || 0 ) + ( g.repaired || 0 );

					let html = `<h2>Results</h2>
					<p><strong>${fmt(n)}</strong> products scanned${ s.total ? ` of <strong>${fmt(s.total)}</strong> reported by Barnet` : '' }.</p>`;

					html += `<h3 style="margin-top:20px">Barcode quality &mdash; decides how well Google Shopping can work</h3>
					<table style="border-collapse:collapse;max-width:760px;width:100%">
						${ bar( 'Valid GTIN', g.valid || 0, n, 'good' ) }
						${ bar( 'Repairable (leading zero)', g.repaired || 0, n, 'warn' ) }
						${ bar( 'Not a valid barcode', g.invalid || 0, n, 'bad' ) }
						${ bar( 'Empty', g.empty || 0, n, 'bad' ) }
					</table>
					<p style="margin-top:8px"><strong>${pct(usable,n).toFixed(1)}%</strong> of products can be submitted to Merchant Center with a real GTIN.
					The remaining <strong>${pct(n-usable,n).toFixed(1)}%</strong> must be declared <code>identifier_exists: false</code>, which still lists them but loses comparison matching.</p>`;

					if ( s.doubled_sku ) {
						html += `<p style="color:#996800">${fmt(s.doubled_sku)} products have a <code>pid</code> that is simply the SKU repeated twice, which is a data-entry pattern rather than random corruption. Worth raising with Barnet.</p>`;
					}

					const f = s.fields || {};
					html += `<h3 style="margin-top:24px">Field population</h3>
					<table style="border-collapse:collapse;max-width:760px;width:100%">
						${ bar( 'Image', f.image || 0, n, ( pct(f.image||0,n) > 90 ? 'good' : 'bad' ) ) }
						${ bar( 'Description (name)', f.description || 0, n, 'good' ) }
						${ bar( 'Long description', f.more_info_html || 0, n ) }
						${ bar( 'Category', f.category_name || 0, n ) }
						${ bar( 'Group', f.group_name || 0, n ) }
						${ bar( 'Producer', f.producer_name || 0, n ) }
						${ bar( 'Vendor', f.vendor_name || 0, n ) }
						${ bar( 'Distributor', f.distributor_name || 0, n ) }
						${ bar( 'Country', f.country_name || 0, n ) }
						${ bar( 'Varietal', f.varital_name || 0, n ) }
						${ bar( 'Alcohol %', f.alcohol || 0, n ) }
						${ bar( 'Sweetness', f.sweetness || 0, n ) }
						${ bar( 'Stock (on_hand)', f.on_hand || 0, n ) }
						${ bar( 'Price', f.sale_price || 0, n, 'good' ) }
					</table>
					<p class="description">Image coverage under 100% means those products cannot go to Merchant Center at all. Producer / vendor / distributor are the brand candidates; whichever is most populated is what we map to Google's brand attribute. Country, varietal, alcohol and sweetness are what storefront filtering would be built from.</p>`;

					if ( s.examples && s.examples.invalid && s.examples.invalid.length ) {
						html += `<h3 style="margin-top:24px">Examples of invalid barcodes</h3>
						<table class="widefat striped" style="max-width:760px"><thead><tr><th>SKU</th><th>pid</th><th>Product</th><th>Why</th></tr></thead><tbody>`;
						s.examples.invalid.forEach( r => {
							html += `<tr><td><code>${r.cspcid}</code></td><td><code>${r.pid}</code></td><td>${r.name}</td><td>${r.reason}</td></tr>`;
						} );
						html += `</tbody></table>`;
					}

					if ( s.examples && s.examples.repaired && s.examples.repaired.length ) {
						html += `<h3 style="margin-top:24px">Repaired by restoring a leading zero</h3>
						<table class="widefat striped" style="max-width:760px"><thead><tr><th>SKU</th><th>Stored pid</th><th>Actual GTIN</th><th>Product</th></tr></thead><tbody>`;
						s.examples.repaired.forEach( r => {
							html += `<tr><td><code>${r.cspcid}</code></td><td><code>${r.pid}</code></td><td><code>${r.gtin}</code></td><td>${r.name}</td></tr>`;
						} );
						html += `</tbody></table>`;
					}

					el('bw-scan-report').innerHTML = html;
				}

				async function batch() {
					const body = new URLSearchParams();
					body.append( 'action', 'bw_barnet_scan_batch' );
					body.append( '_wpnonce', nonce );
					if ( el('bw-web-only').checked ) { body.append( 'web_only', '1' ); }

					const res  = await fetch( ajaxUrl, { method: 'POST', body, credentials: 'same-origin' } );
					const json = await res.json();

					if ( ! json.success ) {
						running = false;
						el('bw-scan-status').textContent = 'Failed: ' + ( json.data && json.data.message ? json.data.message : 'unknown error' );
						el('bw-scan-status').style.color = '#d63638';
						return;
					}

					const s = json.data;
					report( s );

					if ( s.pages ) {
						const done = Math.min( s.next_page - 1, s.pages );
						el('bw-scan-bar').style.width = pct( done, s.pages ).toFixed(1) + '%';
						el('bw-scan-status').textContent = `Page ${done} of ${s.pages} — ${fmt(s.scanned)} products`;
					} else {
						el('bw-scan-status').textContent = `${fmt(s.scanned)} products`;
					}

					if ( s.complete || ! running ) {
						running = false;
						el('bw-scan-status').textContent = `Done — ${fmt(s.scanned)} products scanned`;
						el('bw-scan-status').style.color = '#00a32a';
						return;
					}

					// Small pause between pages. No rate limit is documented, so we
					// deliberately do not hammer the endpoint.
					setTimeout( batch, 250 );
				}

				el('bw-scan-start').addEventListener( 'click', e => {
					e.preventDefault();
					if ( running ) { return; }
					running = true;
					el('bw-scan-status').style.color = '';
					el('bw-scan-status').textContent = 'Starting…';
					el('bw-scan-progress').style.display = 'block';
					batch();
				} );

				el('bw-scan-reset').addEventListener( 'click', async e => {
					e.preventDefault();
					running = false;
					const body = new URLSearchParams();
					body.append( 'action', 'bw_barnet_scan_reset' );
					body.append( '_wpnonce', nonce );
					await fetch( ajaxUrl, { method: 'POST', body, credentials: 'same-origin' } );
					el('bw-scan-report').innerHTML = '';
					el('bw-scan-bar').style.width = '0';
					el('bw-scan-status').textContent = 'Reset.';
					el('bw-scan-status').style.color = '';
				} );

				<?php if ( ! empty( $state['scanned'] ) ) : ?>
				report( <?php echo wp_json_encode( $state ); ?> );
				<?php endif; ?>
			} )();
			</script>
			<?php endif; ?>
		</div>
		<?php
	}
}
