<?php
/**
 * Per-product Split Pay recovery tool — always available.
 *
 * Renders a "Recover Split Pay settings from transfer log" button at the top
 * of the product Split Pay tab, plus surfaces any prior recovery history
 * (the `_bsd_spscwt_recovered_in_374` marker + `_bsd_spscwt_pre_374_state`
 * snapshot left by the 3.7.4 migration or any later manual recovery).
 *
 * Re-uses the same transfer-log scan logic as migration-374.php but scoped
 * to a single product, available perpetually so the merchant can always
 * rebuild the vendor + percentage / amount from historical Stripe
 * transfers regardless of what (mis)configuration produced data loss
 * later. The per-product flow is intentionally idempotent and additive:
 * it never overwrites a non-empty existing configuration unless the user
 * explicitly checks the "Force overwrite" option.
 *
 * Trigger paths:
 *   - admin-ajax handler `spp_recover_product_from_log`
 *   - REST not used (avoids permission-policy complexity)
 *
 * @since 3.7.4
 * @package BSD_Split_Pay_Stripe_Connect_Woo
 */

namespace BSD_Split_Pay_Stripe_Connect_Woo\Inc\Admin;

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

add_action( 'woocommerce_product_data_panels', __NAMESPACE__ . '\\spp_render_recovery_panel', 5 );
add_action( 'wp_ajax_spp_recover_product_from_log', __NAMESPACE__ . '\\spp_ajax_recover_product_from_log' );

/**
 * Render the recovery UI at the very top of the Split Pay product data panel.
 * Output order: status row (already-recovered marker if present), recover
 * button, optional "force overwrite" checkbox.
 *
 * Runs at priority 5 (before renderProductDataFields at default 10) so the
 * recovery UI always appears above the rows themselves.
 *
 * @return void
 */
function spp_render_recovery_panel(): void {
	global $post;

	if ( ! $post || ! isset( $post->ID ) ) {
		return;
	}
	if ( ! current_user_can( 'edit_post', $post->ID ) ) {
		return;
	}

	$marker  = get_post_meta( $post->ID, '_bsd_spscwt_recovered_in_374', true );
	$snap    = get_post_meta( $post->ID, '_bsd_spscwt_pre_374_state', true );
	$current = get_post_meta( $post->ID, '_bsd_spscwt_product_connected_account', true );

	$has_existing_meta = is_array( $current ) && array_filter( $current, function ( $v ) { return ! empty( $v ); } );

	$nonce = wp_create_nonce( 'spp_recover_product_' . $post->ID );

	?>
	<div id="spp_recovery_panel" class="spp-recovery-wrap" style="display:none;">
		<div class="options_group" style="padding:12px 12px 8px;">
			<p style="margin:0 0 6px;">
				<strong><?php esc_html_e( 'Recover Split Pay settings from transfer log', 'bsd-split-pay-stripe-connect-woo' ); ?></strong>
				&nbsp;<a href="https://docs.splitpayplugin.com/support/recovering-lost-product-settings/" target="_blank" rel="noopener noreferrer" style="font-weight:normal;"><?php esc_html_e( 'Learn more →', 'bsd-split-pay-stripe-connect-woo' ); ?></a>
			</p>
			<p class="description" style="margin:0 0 8px;">
				<?php esc_html_e( 'Rebuilds this product\'s vendor + percentage/amount from the most recent Stripe transfer that moved money for it. Safe to re-run anytime.', 'bsd-split-pay-stripe-connect-woo' ); ?>
			</p>

			<?php if ( is_array( $marker ) && ! empty( $marker ) ) : ?>
				<div style="background:#f6f7f7;border-left:3px solid #2271b1;padding:8px 10px;margin:8px 0;">
					<p style="margin:0 0 4px;"><strong><?php esc_html_e( 'Last recovery on this product', 'bsd-split-pay-stripe-connect-woo' ); ?></strong></p>
					<p style="margin:0;">
						<?php
						printf(
							/* translators: 1: ISO date, 2: vendor acct id, 3: entered value, 4: type (percentage/amount) */
							esc_html__( 'Recovered on %1$s — vendor %2$s, value %3$s (%4$s). Sourced from Stripe transfer dated %5$s.', 'bsd-split-pay-stripe-connect-woo' ),
							esc_html( (string) ( $marker['recovered_at'] ?? '?' ) ),
							'<code>' . esc_html( (string) ( $marker['vendor_acct'] ?? '?' ) ) . '</code>',
							esc_html( (string) ( $marker['entered_value'] ?? '?' ) ),
							esc_html( (string) ( $marker['transfer_type_str'] ?? '?' ) ),
							esc_html( (string) ( $marker['source_charge_date'] ?? '?' ) )
						);
						?>
					</p>
				</div>
			<?php endif; ?>

			<?php if ( $has_existing_meta ) : ?>
				<p style="margin:8px 0;">
					<label style="display:inline-block;">
						<input type="checkbox" id="spp_recover_force_overwrite" />
						<?php esc_html_e( 'Force overwrite the current vendor (otherwise skipped if anything is already set).', 'bsd-split-pay-stripe-connect-woo' ); ?>
					</label>
				</p>
			<?php endif; ?>

			<p style="margin:10px 0 0;">
				<button type="button"
					id="spp_recover_button"
					class="button button-secondary"
					data-product-id="<?php echo esc_attr( (int) $post->ID ); ?>"
					data-nonce="<?php echo esc_attr( $nonce ); ?>">
					<?php esc_html_e( 'Run recovery from transfer log', 'bsd-split-pay-stripe-connect-woo' ); ?>
				</button>
				<span id="spp_recover_result" style="margin-left:10px;"></span>
			</p>
		</div>
	</div>
	<script>
	(function ($) {
		'use strict';
		// Inject the recovery panel as the FIRST child of the SPP product data panel.
		$(function () {
			var $spp = $('#stripe_connect_split_pay_product_data');
			var $rec = $('#spp_recovery_panel');
			if ( $spp.length && $rec.length ) {
				$rec.detach().prependTo( $spp ).show();
			}

			$('#spp_recover_button').on('click', function () {
				var $btn = $(this);
				var $res = $('#spp_recover_result');
				$res.text('<?php echo esc_js( __( 'Recovering…', 'bsd-split-pay-stripe-connect-woo' ) ); ?>').css('color', '');
				$btn.prop('disabled', true);

				$.post(ajaxurl, {
					action: 'spp_recover_product_from_log',
					product_id: $btn.data('productId'),
					_ajax_nonce: $btn.data('nonce'),
					force: $('#spp_recover_force_overwrite').is(':checked') ? '1' : '0'
				}).done(function (resp) {
					if (resp && resp.success) {
						var d = resp.data || {};
						if (d.recovered) {
							$res.css('color', '#1e7e34').text(d.message || '<?php echo esc_js( __( 'Recovered. Reload this page to see the restored values.', 'bsd-split-pay-stripe-connect-woo' ) ); ?>');
						} else {
							$res.css('color', '#646970').text(d.message || '<?php echo esc_js( __( 'Nothing to recover.', 'bsd-split-pay-stripe-connect-woo' ) ); ?>');
						}
					} else {
						var msg = (resp && resp.data && resp.data.message) ? resp.data.message : '<?php echo esc_js( __( 'Recovery failed.', 'bsd-split-pay-stripe-connect-woo' ) ); ?>';
						$res.css('color', '#b32d2e').text(msg);
					}
				}).fail(function () {
					$res.css('color', '#b32d2e').text('<?php echo esc_js( __( 'Recovery request failed (network or server error).', 'bsd-split-pay-stripe-connect-woo' ) ); ?>');
				}).always(function () {
					$btn.prop('disabled', false);
				});
			});
		});
	})(jQuery);
	</script>
	<?php
}

/**
 * AJAX handler: recover one product's Split Pay settings from the transfer log.
 *
 * Mirrors the migration-374 algorithm (single-product orders unambiguous;
 * multi-product orders only if a single shared destination + value), but
 * filters to ONLY the transfers tied to this product via its order line
 * items.
 *
 * @return void
 */
function spp_ajax_recover_product_from_log(): void {
	$product_id = isset( $_POST['product_id'] ) ? (int) $_POST['product_id'] : 0;
	$force      = ! empty( $_POST['force'] );

	if ( ! $product_id || ! current_user_can( 'edit_post', $product_id ) ) {
		wp_send_json_error( [ 'message' => __( 'Permission denied.', 'bsd-split-pay-stripe-connect-woo' ) ], 403 );
	}

	check_ajax_referer( 'spp_recover_product_' . $product_id );

	if ( ! function_exists( 'wc_get_order' ) ) {
		wp_send_json_error( [ 'message' => __( 'WooCommerce must be active to run recovery.', 'bsd-split-pay-stripe-connect-woo' ) ] );
	}

	global $wpdb;
	$log_table = $wpdb->prefix . 'bsd_scsp_transfer_log';

	if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $log_table ) ) !== $log_table ) {
		wp_send_json_error( [ 'message' => __( 'Transfer log table not found.', 'bsd-split-pay-stripe-connect-woo' ) ] );
	}

	$existing = get_post_meta( $product_id, '_bsd_spscwt_product_connected_account', true );
	$has_existing = is_array( $existing ) && array_filter( $existing, function ( $v ) { return ! empty( $v ); } );

	if ( $has_existing && ! $force ) {
		wp_send_json_success( [
			'recovered' => false,
			'message'   => __( 'This product already has a vendor configured — skipping (check Force overwrite to override).', 'bsd-split-pay-stripe-connect-woo' ),
		] );
	}

	$candidate = spp_find_recovery_candidate_for_product( $product_id );

	if ( ! $candidate ) {
		wp_send_json_success( [
			'recovered' => false,
			'message'   => __( 'No historical Stripe transfer found for this product in the log. Nothing to recover from.', 'bsd-split-pay-stripe-connect-woo' ),
		] );
	}

	$tt     = (int) $candidate['transfer_type'];
	$is_pct = in_array( $tt, [ 1, 10, 8 ], true );
	$pt     = $is_pct ? 'percentage' : 'amount';
	$value  = (string) $candidate['transfer_entered_value'];

	// Forensic snapshot of the pre-recovery state. Always written so a manual
	// recovery can also be reverted later.
	update_post_meta( $product_id, '_bsd_spscwt_pre_374_state', [
		'snapshot_at' => current_time( 'mysql', true ),
		'connected'   => $existing,
		'type'        => get_post_meta( $product_id, '_bsd_spscwt_product_type', true ),
		'pct'         => get_post_meta( $product_id, '_stripe_connect_split_pay_transfer_percentage', true ),
		'amt'         => get_post_meta( $product_id, '_bsd_spscwt_product_amount', true ),
		'ship_type'   => get_post_meta( $product_id, '_bsd_spscwt_shipping_type', true ),
		'ship_pct'    => get_post_meta( $product_id, '_bsd_prod_shipping_percentage', true ),
		'ship_amt'    => get_post_meta( $product_id, '_bsd_spscwt_shipping_amount', true ),
		'source'      => 'manual_per_product_button',
		'force'       => $force,
	] );

	update_post_meta( $product_id, '_bsd_spscwt_product_connected_account', [ 0 => $candidate['transfer_destination'] ] );
	update_post_meta( $product_id, '_bsd_spscwt_product_type', [ 0 => $pt ] );
	if ( $is_pct ) {
		update_post_meta( $product_id, '_stripe_connect_split_pay_transfer_percentage', [ 0 => $value ] );
	} else {
		update_post_meta( $product_id, '_bsd_spscwt_product_amount', [ 0 => $value ] );
	}

	update_post_meta( $product_id, '_bsd_spscwt_recovered_in_374', [
		'recovered_at'       => current_time( 'mysql', true ),
		'source_order_id'    => (int) $candidate['source_order_id'],
		'source_charge_date' => $candidate['charge_date'],
		'vendor_acct'        => $candidate['transfer_destination'],
		'entered_value'      => $value,
		'transfer_type_int'  => $tt,
		'transfer_type_str'  => $pt,
		'source'             => 'manual_per_product_button',
		'force'              => $force,
	] );

	wp_send_json_success( [
		'recovered' => true,
		'message'   => sprintf(
			/* translators: 1: vendor acct id, 2: value, 3: type label */
			__( 'Recovered. Vendor %1$s with %2$s (%3$s) restored from your transfer log. Reload to see it on the form.', 'bsd-split-pay-stripe-connect-woo' ),
			$candidate['transfer_destination'],
			$value,
			$pt
		),
	] );
}

/**
 * Find the most recent recovery candidate for a single product.
 *
 * Scans the transfer log for orders that contain this product's id as a
 * line item product or variation parent, then applies the same unambiguous-
 * mapping rules as migration-374 (single-line orders OR multi-line orders
 * with a single shared destination + value).
 *
 * Returns null when no usable historical mapping exists.
 *
 * @param int $product_id Product or variation ID.
 * @return array<string, mixed>|null
 */
function spp_find_recovery_candidate_for_product( int $product_id ): ?array {
	global $wpdb;
	$log_table = $wpdb->prefix . 'bsd_scsp_transfer_log';

	// Pull candidate orders by walking transfer_log rows newest-first. The
	// 5000-row cap mirrors the migration; in practice a single product won't
	// touch anywhere near this.
	// $log_table is a constant table name ($wpdb->prefix . 'bsd_scsp_transfer_log'); this fixed SELECT has no bindable user input.
	// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- constant table identifier, no user input.
	$order_ids = $wpdb->get_col(
		"SELECT DISTINCT wc_order_id
		 FROM {$log_table}
		 WHERE wc_order_id IS NOT NULL
		   AND wc_order_id > 0
		   AND transfer_destination IS NOT NULL
		   AND transfer_destination != ''
		 ORDER BY id DESC
		 LIMIT 5000"
	);

	if ( empty( $order_ids ) ) {
		return null;
	}

	$best = null;

	foreach ( $order_ids as $oid ) {
		$oid = (int) $oid;

		$order = wc_get_order( $oid );
		if ( ! $order ) {
			continue;
		}

		$pids = [];
		foreach ( $order->get_items() as $item ) {
			if ( ! is_object( $item ) ) { continue; }
			if ( method_exists( $item, 'get_variation_id' ) ) {
				$vid = (int) $item->get_variation_id();
				if ( $vid > 0 ) { $pids[] = $vid; }
			}
			if ( method_exists( $item, 'get_product_id' ) ) {
				$pid = (int) $item->get_product_id();
				if ( $pid > 0 ) { $pids[] = $pid; }
			}
		}
		$pids = array_unique( $pids );

		if ( ! in_array( $product_id, $pids, true ) ) {
			continue;
		}

		// $log_table is a constant table name ($wpdb->prefix . 'bsd_scsp_transfer_log'); $oid is bound via the %d placeholder in prepare().
		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- constant table identifier; user value bound via %d placeholder.
		$transfers = $wpdb->get_results( $wpdb->prepare(
			"SELECT wc_order_id, transfer_destination, transfer_entered_value,
			        transfer_type, charge_date, transfer_amount
			 FROM {$log_table}
			 WHERE wc_order_id = %d
			   AND transfer_destination IS NOT NULL
			   AND transfer_destination != ''
			   AND transfer_type IN (0, 1, 8, 10, 11, 12)
			 ORDER BY id ASC",
			$oid
		) );

		if ( empty( $transfers ) ) {
			continue;
		}

		// Pick a transfer we can confidently attribute to THIS product:
		//   - single-line order → any transfer in this order maps to it
		//   - multi-line order → only if all transfers share one destination+value
		$single_line = ( count( $pids ) === 1 );
		$single_dest = ( count( array_unique( array_map( static function ( $t ) { return $t->transfer_destination; }, $transfers ) ) ) === 1 );
		$single_val  = ( count( array_unique( array_map( static function ( $t ) { return (string) $t->transfer_entered_value; }, $transfers ) ) ) === 1 );
		$single_type = ( count( array_unique( array_map( static function ( $t ) { return (string) $t->transfer_type; }, $transfers ) ) ) === 1 );

		if ( ! $single_line && ! ( $single_dest && $single_val && $single_type ) ) {
			continue;
		}

		$candidate = (array) $transfers[0];
		$candidate['source_order_id'] = $oid;

		if ( $best === null || strtotime( (string) $candidate['charge_date'] ) > strtotime( (string) $best['charge_date'] ) ) {
			$best = $candidate;
		}
	}

	return $best;
}
