<?php
/**
 * A1-WooBay - eBay Marketplace Account Deletion Compliance
 *
 * Implements the eBay Marketplace Account Deletion/Closure notification
 * endpoint required for all apps using eBay APIs.
 *
 * Endpoint: GET  /wp-json/a1-woobay/v1/account-deletion/{secret}  (challenge verification)
 *           POST /wp-json/a1-woobay/v1/account-deletion/{secret}  (deletion notification)
 *
 * eBay's own notification protocol has no per-request signature on the POST
 * notification itself - only the GET challenge is authenticated (via the
 * verification token baked into the SHA-256 hash). Since this plugin is
 * open source, the *route path* is publicly readable and can't be treated
 * as secret on its own. The {secret} path segment is a random, per-site
 * token generated on first use (see get_or_create_secret()) and checked on
 * every request - it's what makes the *specific, registered* endpoint URL
 * unguessable, which is the only real protection eBay's protocol leaves
 * available to us. The site owner registers the full URL (secret included)
 * with eBay; see get_endpoint_url().
 *
 * @package A1_WooBay
 * @since   1.0.6
 * @see https://developer.ebay.com/marketplace-account-deletion
 */

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

class A1WooBay_Compliance {

	const OPT_VERIFICATION_TOKEN = 'a1woobay_ebay_verification_token';
	const OPT_ENDPOINT_SECRET    = 'a1woobay_ebay_endpoint_secret';
	const OPT_DELETION_LOG       = 'a1woobay_deletion_log';
	const REST_NAMESPACE         = 'a1-woobay/v1';
	const REST_ROUTE             = '/account-deletion/(?P<secret>[a-zA-Z0-9]{32,64})';

	private static ?A1WooBay_Compliance $instance = null;

	public static function get_instance(): A1WooBay_Compliance {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	private function __construct() {
		add_action( 'rest_api_init', array( $this, 'register_routes' ) );
	}

	// -------------------------------------------------------------------------
	// REST route registration
	// -------------------------------------------------------------------------

	public function register_routes(): void {
		register_rest_route(
			self::REST_NAMESPACE,
			self::REST_ROUTE,
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'handle_challenge' ),
					'permission_callback' => array( $this, 'verify_secret' ),
				),
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'handle_deletion' ),
					'permission_callback' => array( $this, 'verify_secret' ),
				),
			)
		);
	}

	/**
	 * Route-level permission check. Both methods are otherwise unauthenticated
	 * by design (required by eBay's protocol - see the class docblock), so the
	 * {secret} path segment matching the per-site generated value is the only
	 * gate available. Uses hash_equals() for a timing-safe comparison.
	 */
	public function verify_secret( WP_REST_Request $request ): bool {
		$given  = (string) $request->get_param( 'secret' );
		$actual = self::get_or_create_secret();
		return $given && hash_equals( $actual, $given );
	}

	// -------------------------------------------------------------------------
	// GET: challenge verification
	// -------------------------------------------------------------------------

	/**
	 * Respond to eBay's endpoint verification challenge.
	 *
	 * eBay sends: GET ?challenge_code=<code>
	 * We respond: { "challengeResponse": SHA256( challengeCode + verificationToken + endpointUrl ) }
	 */
	public function handle_challenge( WP_REST_Request $request ): WP_REST_Response {
		$challenge_code = sanitize_text_field( $request->get_param( 'challenge_code' ) ?? '' );

		if ( ! $challenge_code ) {
			return new WP_REST_Response( array( 'error' => 'Missing challenge_code.' ), 400 );
		}

		$token = get_option( self::OPT_VERIFICATION_TOKEN, '' );
		if ( ! $token ) {
			return new WP_REST_Response( array( 'error' => 'Verification token not configured. Set it in A1 Marketplace Sync with eBay for WooCommerce → Settings.' ), 500 );
		}

		$endpoint_url = self::get_endpoint_url();
		$hash         = hash( 'sha256', $challenge_code . $token . $endpoint_url );

		return new WP_REST_Response( array( 'challengeResponse' => $hash ), 200 );
	}

	// -------------------------------------------------------------------------
	// POST: account deletion notification
	// -------------------------------------------------------------------------

	/**
	 * Handle an account deletion/closure notification from eBay.
	 * Must respond 200 quickly; actual data work happens synchronously
	 * (acceptable for small stores — large stores could defer to a queue).
	 */
	public function handle_deletion( WP_REST_Request $request ): WP_REST_Response {
		$body = $request->get_json_params();

		// Ignore anything that doesn't look like a real eBay notification.
		// Bots and scanners POST to public REST endpoints but won't have this structure.
		if ( empty( $body['notification']['data'] ) || empty( $body['notification']['notificationId'] ) ) {
			return new WP_REST_Response( null, 200 );
		}

		$data     = $body['notification']['data'];
		$username = sanitize_text_field( $data['username'] ?? '' );
		$user_id  = sanitize_text_field( $data['userId']   ?? '' );

		// Require at least a userId to consider this a real deletion request.
		if ( ! $user_id ) {
			return new WP_REST_Response( null, 200 );
		}

		$anonymized = 0;
		if ( $username ) {
			$anonymized = $this->anonymize_buyer_orders( $username );
		}

		// Log the request (keep last 50 entries)
		$log   = get_option( self::OPT_DELETION_LOG, array() );
		$log[] = array(
			'time'        => current_time( 'mysql' ),
			'username'    => $username ?: '(unknown)',
			'userId'      => $user_id,
			'anonymized'  => $anonymized,
		);
		update_option( self::OPT_DELETION_LOG, array_slice( $log, -50 ) );

		// eBay requires a 200 response; no body needed.
		return new WP_REST_Response( null, 200 );
	}

	// -------------------------------------------------------------------------
	// Data anonymization
	// -------------------------------------------------------------------------

	/**
	 * Find WooCommerce orders imported from eBay for this buyer and anonymize
	 * their personal data. Returns the number of orders anonymized.
	 *
	 * @param string $username eBay buyer username stored on order import.
	 * @return int
	 */
	private function anonymize_buyer_orders( string $username ): int {
		$orders = wc_get_orders( array(
			'limit'      => -1,
			'return'     => 'ids',
			'meta_key'   => A1WooBay_Order_Meta::META_EBAY_BUYER_USERNAME,
			'meta_value' => $username,
		) );

		$count = 0;
		foreach ( $orders as $order_id ) {
			$order = wc_get_order( $order_id );
			if ( ! $order ) {
				continue;
			}

			// Clear personal data from billing address
			$order->set_billing_first_name( 'Deleted' );
			$order->set_billing_last_name( 'eBay User' );
			$order->set_billing_email( 'deleted-ebay-user@account.invalid' );
			$order->set_billing_address_1( '' );
			$order->set_billing_address_2( '' );
			$order->set_billing_city( '' );
			$order->set_billing_state( '' );
			$order->set_billing_postcode( '' );
			$order->set_billing_phone( '' );

			// Clear personal data from shipping address
			$order->set_shipping_first_name( 'Deleted' );
			$order->set_shipping_last_name( 'eBay User' );
			$order->set_shipping_address_1( '' );
			$order->set_shipping_address_2( '' );
			$order->set_shipping_city( '' );
			$order->set_shipping_state( '' );
			$order->set_shipping_postcode( '' );

			// Remove the buyer username meta so it can't be re-matched
			$order->delete_meta_data( A1WooBay_Order_Meta::META_EBAY_BUYER_USERNAME );
			$order->add_order_note( 'Personal data anonymized per eBay Marketplace Account Deletion request.' );
			$order->save();
			$count++;
		}

		return $count;
	}

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

	public static function get_endpoint_url(): string {
		return rest_url( self::REST_NAMESPACE . '/account-deletion/' . self::get_or_create_secret() );
	}

	/**
	 * Returns this site's endpoint secret, generating and storing one on
	 * first call if it doesn't exist yet. 48 hex chars (24 random bytes) -
	 * comfortably within the route pattern's [a-zA-Z0-9]{32,64} bound.
	 */
	public static function get_or_create_secret(): string {
		$secret = get_option( self::OPT_ENDPOINT_SECRET, '' );
		if ( ! $secret ) {
			$secret = bin2hex( random_bytes( 24 ) );
			update_option( self::OPT_ENDPOINT_SECRET, $secret );
		}
		return $secret;
	}
}
