<?php
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * GDPR / WP-Privacy-API integration:
 * - Suggests privacy policy content (Tools → Privacy editor).
 * - Registers personal-data exporter so admins can fulfill access requests.
 * - Registers personal-data eraser so admins can fulfill deletion requests.
 *
 * Free tier: only an anonymous visitor cookie + favorites table exists. That data is
 *            by design not linkable to an email address — the exporter returns an
 *            empty record set with a note explaining this.
 * Pro tier:  print orders are stored with full customer contact details, looked up
 *            and exported/erased by the customer's email. The Pro statistics module
 *            adds visitor view/download counts to its own privacy disclosures.
 */
class CCGAL_Privacy {

	const EXPORTER_KEY = 'customer-cloud-gallery';
	const ERASER_KEY   = 'customer-cloud-gallery';

	public static function init() {
		add_action( 'admin_init', array( __CLASS__, 'add_privacy_policy_content' ) );
		add_filter( 'wp_privacy_personal_data_exporters', array( __CLASS__, 'register_exporter' ) );
		add_filter( 'wp_privacy_personal_data_erasers',   array( __CLASS__, 'register_eraser' ) );
	}

	public static function add_privacy_policy_content() {
		if ( ! function_exists( 'wp_add_privacy_policy_content' ) ) {
			return;
		}

		// Single-string i18n call so the POT extractor catches it as one msgid.
		// Note: the cookie name is intentionally `wpg_visitor` (not the `ccgal_`
		// prefix used everywhere else) — renaming would orphan favourites of
		// visitors who already have the cookie set from a previous gallery
		// visit. See CCGAL_Favorites::COOKIE_NAME and the 1.12.1 changelog.
		$content = __( '<strong>Customer Cloud Gallery</strong> stores limited data needed to make galleries function. <strong>Visitor cookie (`wpg_visitor`):</strong> a random hexadecimal identifier, generated server-side, valid for 365 days. Used so favorites stay associated with the visitor across page reloads. The cookie is set only when a gallery shortcode is rendered with the per-gallery cookies switch enabled. <strong>Favorites:</strong> when a visitor hearts an image, a row is written to the favorites table containing the gallery ID, the cloud file ID, and the random visitor cookie value. No email address, no name, no plain-text IP address is stored. <strong>Print orders (Pro add-on):</strong> when the Pro print-shop is active and a customer places an order, their submitted name, email, shipping address, optional phone number and message are stored in the orders table along with the chosen formats, paper types and quantities; this data is retained until the order is deleted in the WordPress admin. Personal data export and erasure requests submitted via Tools → Export/Erase Personal Data will be fulfilled: print orders are returned/deleted by email; the anonymous visitor cookie cannot be linked to an email address and the response will note this. The plugin connects to Google Drive and/or Dropbox using credentials provided by the site owner. Visitor data is never sent to those services — only the gallery owner\'s API tokens are exchanged with them.', 'customer-cloud-gallery' );

		$content = wp_kses_post( wpautop( wp_kses( $content, array(
			'strong' => array(),
			'p'      => array(),
			'code'   => array(),
		) ) ) );

		wp_add_privacy_policy_content( 'Customer Cloud Gallery', $content );
	}

	public static function register_exporter( $exporters ) {
		$exporters[ self::EXPORTER_KEY ] = array(
			'exporter_friendly_name' => __( 'Customer Cloud Gallery', 'customer-cloud-gallery' ),
			'callback'               => array( __CLASS__, 'export_personal_data' ),
		);
		return $exporters;
	}

	public static function register_eraser( $erasers ) {
		$erasers[ self::ERASER_KEY ] = array(
			'eraser_friendly_name' => __( 'Customer Cloud Gallery', 'customer-cloud-gallery' ),
			'callback'             => array( __CLASS__, 'erase_personal_data' ),
		);
		return $erasers;
	}

	/**
	 * Personal data exporter callback.
	 *
	 * @param string $email_address The email being exported.
	 * @param int    $page          1-based page number.
	 * @return array { data: WP_Personal_Data_Item[], done: bool }
	 */
	public static function export_personal_data( $email_address, $page = 1 ) {
		$page = max( 1, (int) $page );
		$data = array();

		// Anonymous visitor cookie — explicitly note that favourites cannot be linked to email.
		$data[] = array(
			'group_id'    => 'ccgal_anonymous_favorites',
			'group_label' => __( 'Customer Cloud Gallery — anonymous favorites', 'customer-cloud-gallery' ),
			'item_id'     => 'ccgal-anon-note-' . md5( $email_address ),
			'data'        => array(
				array(
					'name'  => __( 'Note', 'customer-cloud-gallery' ),
					'value' => __( 'Galleries set an anonymous random cookie to remember favourites. The associated database records (favorites only) are stored against that random cookie value, never against an email address. No data was found for this email in the anonymous favorites table.', 'customer-cloud-gallery' ),
				),
			),
		);

		// Pro: also export print orders for this email.
		if ( function_exists( 'ccgal_is_pro' ) && ccgal_is_pro() ) {
			$data = array_merge( $data, self::export_print_orders( $email_address ) );
		}

		return array(
			'data' => $data,
			'done' => true,
		);
	}

	/**
	 * Personal data eraser callback.
	 *
	 * @param string $email_address The email being erased.
	 * @param int    $page          1-based page number.
	 * @return array { items_removed, items_retained, messages, done }
	 */
	public static function erase_personal_data( $email_address, $page = 1 ) {
		$page            = max( 1, (int) $page );
		$items_removed   = false;
		$items_retained  = false;
		$messages        = array();

		// The anonymous visitor cookie + favorites are not linkable to email — retain with note.
		$items_retained = true;
		$messages[]     = __( 'Customer Cloud Gallery\'s anonymous favorites are stored against a random cookie ID, not against any email. They were not erased because there is no way to identify which (if any) records belong to this email address.', 'customer-cloud-gallery' );

		// Pro: erase print orders for this email.
		if ( function_exists( 'ccgal_is_pro' ) && ccgal_is_pro() ) {
			$result = self::erase_print_orders( $email_address );
			if ( $result['removed'] > 0 ) {
				$items_removed = true;
				$messages[]    = sprintf(
					/* translators: %d: number of erased orders */
					_n( 'Erased %d print order linked to this email.', 'Erased %d print orders linked to this email.', $result['removed'], 'customer-cloud-gallery' ),
					$result['removed']
				);
			}
			if ( $result['retained'] > 0 ) {
				$items_retained = true;
				$messages[]     = sprintf(
					/* translators: %d: number of retained orders */
					_n( '%d print order was retained because it has not been completed yet.', '%d print orders were retained because they have not been completed yet.', $result['retained'], 'customer-cloud-gallery' ),
					$result['retained']
				);
			}
		}

		return array(
			'items_removed'  => $items_removed,
			'items_retained' => $items_retained,
			'messages'       => $messages,
			'done'           => true,
		);
	}

	private static function export_print_orders( $email_address ) {
		global $wpdb;

		$orders_table = $wpdb->prefix . 'ccgal_orders';
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- one-shot lookup for a privacy export request on a plugin-owned custom table; $orders_table comes from $wpdb->prefix; $email_address is bound via $wpdb->prepare(); caching makes no sense.
		$orders = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$orders_table} WHERE customer_email = %s ORDER BY created_at ASC", $email_address ), ARRAY_A );

		$exports = array();
		if ( empty( $orders ) ) {
			return $exports;
		}

		foreach ( $orders as $order ) {
			$exports[] = array(
				'group_id'    => 'ccgal_print_orders',
				'group_label' => __( 'Customer Cloud Gallery — print orders', 'customer-cloud-gallery' ),
				'item_id'     => 'ccgal-order-' . (int) $order['id'],
				'data'        => array(
					array( 'name' => __( 'Order ID', 'customer-cloud-gallery' ),       'value' => (int) $order['id'] ),
					array( 'name' => __( 'Date', 'customer-cloud-gallery' ),           'value' => $order['created_at'] ),
					array( 'name' => __( 'Customer name', 'customer-cloud-gallery' ),  'value' => $order['customer_name'] ),
					array( 'name' => __( 'Email', 'customer-cloud-gallery' ),          'value' => $order['customer_email'] ),
					array( 'name' => __( 'Phone', 'customer-cloud-gallery' ),          'value' => (string) $order['customer_phone'] ),
					array( 'name' => __( 'Shipping address', 'customer-cloud-gallery' ), 'value' => $order['customer_address'] ),
					array( 'name' => __( 'Message', 'customer-cloud-gallery' ),        'value' => (string) $order['customer_message'] ),
					array( 'name' => __( 'Total', 'customer-cloud-gallery' ),          'value' => $order['total'] ),
					array( 'name' => __( 'Status', 'customer-cloud-gallery' ),         'value' => $order['status'] ),
					array( 'name' => __( 'Payment status', 'customer-cloud-gallery' ), 'value' => $order['payment_status'] ),
				),
			);
		}

		return $exports;
	}

	private static function erase_print_orders( $email_address ) {
		global $wpdb;

		$orders_table = $wpdb->prefix . 'ccgal_orders';
		$items_table  = $wpdb->prefix . 'ccgal_order_items';

		// Only delete orders that are completed/cancelled — pending orders may still need fulfillment.
		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
		// Justification: privacy erasure on plugin-owned custom tables; $orders_table comes from $wpdb->prefix plus a constant suffix; $email_address is bound via $wpdb->prepare(); caching makes no sense for one-shot privacy requests.
		$completed_ids = $wpdb->get_col( $wpdb->prepare(
			"SELECT id FROM {$orders_table} WHERE customer_email = %s AND status IN ('erledigt','storniert')",
			$email_address
		) );

		$retained = (int) $wpdb->get_var( $wpdb->prepare(
			"SELECT COUNT(*) FROM {$orders_table} WHERE customer_email = %s AND status NOT IN ('erledigt','storniert')",
			$email_address
		) );
		// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter

		$removed = 0;
		if ( ! empty( $completed_ids ) ) {
			$completed_ids = array_map( 'intval', $completed_ids );
			$ids_in        = implode( ',', $completed_ids );
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- $ids_in is composed of intval-cast integers only; $items_table comes from $wpdb->prefix.
			$wpdb->query( "DELETE FROM {$items_table} WHERE order_id IN ({$ids_in})" );
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- see above
			$removed = (int) $wpdb->query( "DELETE FROM {$orders_table} WHERE id IN ({$ids_in})" );
		}

		return array(
			'removed'  => $removed,
			'retained' => $retained,
		);
	}
}
