<?php
/**
 * Freemius SDK integration.
 *
 * @package Harbor_EU_EInvoicing_For_WooCommerce
 */

defined( 'ABSPATH' ) || exit;

/**
 * Credentials come from the Freemius dashboard (product Settings -> API & Keys). The public key is
 * shippable by design; the secret key must never appear here. Both can be overridden in wp-config
 */
if ( ! defined( 'HEIN_FS_PRODUCT_ID' ) ) {
	define( 'HEIN_FS_PRODUCT_ID', 34653 );
}
if ( ! defined( 'HEIN_FS_PUBLIC_KEY' ) ) {
	define( 'HEIN_FS_PUBLIC_KEY', 'pk_29f812ee6b66f800256224bccb260' );
}

class HEIN_Freemius {

	/**
	 * @var \Freemius|null
	 */
	private static $instance = null;

	public static function init(): void {
		if ( null !== self::$instance ) {
			return;
		}

		// Freemius::instance() returns false for an unregistered product id, and the SDK then calls
		// dynamic_init() on it and fatals. Until the product exists in the Freemius dashboard the
		// credentials are placeholders, so the whole SDK is skipped and the plugin runs as free
		if ( HEIN_FS_PRODUCT_ID <= 0 || '' === HEIN_FS_PUBLIC_KEY ) {
			return;
		}

		if ( ! function_exists( 'fs_dynamic_init' ) ) {
			$sdk = HEIN_PATH . 'vendor/freemius/wordpress-sdk/start.php';

			// A git clone without `composer install` must degrade to free, never fatal
			if ( ! file_exists( $sdk ) ) {
				return;
			}

			require_once $sdk;
		}

		// Freemius::instance() returns false for a product id the SDK cannot resolve and then calls
		// dynamic_init() on it, so a mistyped constant is a fatal, not an error return. Licensing is
		// never worth white-screening a store over
		try {
			self::$instance = fs_dynamic_init(
				[
					'id'                  => HEIN_FS_PRODUCT_ID,
					'slug'                => 'harbor-eu-einvoicing-for-woocommerce',
					'type'                => 'plugin',
					'public_key'          => HEIN_FS_PUBLIC_KEY,
					// This codebase is the premium source; Freemius deployment rewrites the flag per
					// build, and false is the safe default for the code that ships to wp.org
					'is_premium'          => false,
					'premium_suffix'      => 'Pro',
					'has_premium_version' => true,
					'has_paid_plans'      => true,
					'is_live'             => true,
					'menu'                => [
						'slug'   => 'hein-settings',
						'parent' => [
							'slug' => 'woocommerce',
						],
					],
				]
			);
		} catch ( Throwable $e ) {
			self::$instance = null;

			return;
		}

		// WP runs uninstall.php INSTEAD of registered uninstall hooks when that file exists, and a
		// zip containing one is rejected by Freemius deployment, so uninstall lives here
		self::$instance->add_action( 'after_uninstall', [ __CLASS__, 'uninstall_cleanup' ] );
	}

	public static function instance() {
		return self::$instance;
	}

	public static function uninstall_cleanup(): void {
		global $wpdb;

		if ( ! is_multisite() ) {
			self::uninstall_site();

			return;
		}

		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$site_ids = $wpdb->get_col( "SELECT blog_id FROM {$wpdb->blogs}" );

		foreach ( $site_ids as $site_id ) {
			switch_to_blog( (int) $site_id );
			self::uninstall_site();
			restore_current_blog();
		}
	}

	/**
	 * Invoices are legally retained records, so dropping the tables is opt-in
	 */
	private static function uninstall_site(): void {
		global $wpdb;

		$settings = get_option( 'hein_settings', [] );
		$purge    = is_array( $settings ) && ! empty( $settings['delete_data_on_uninstall'] );

		delete_option( 'hein_settings' );
		delete_option( 'hein_db_version' );

		$wpdb->query(
			$wpdb->prepare(
				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
				"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
				$wpdb->esc_like( '_transient_hein_' ) . '%',
				$wpdb->esc_like( '_transient_timeout_hein_' ) . '%'
			)
		);

		if ( ! $purge ) {
			return;
		}

		foreach ( [ hein_invoices_table(), hein_numbers_table(), hein_counters_table() ] as $table ) {
			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.SchemaChange
			$wpdb->query( "DROP TABLE IF EXISTS {$table}" );
		}

		self::purge_files();
	}

	/**
	 * The uploads mirror holds buyer names, addresses, VAT ids and amounts, so leaving it behind
	 * would defeat the erasure the purge setting exists for and orphan the data with no plugin left
	 * to manage it. switch_to_blog() has already repointed wp_upload_dir(), so each site of a
	 * network resolves its own directory
	 */
	private static function purge_files(): void {
		$dir  = HEIN_Invoice_Store::dir();
		$name = basename( untrailingslashit( $dir ) );

		// Whatever an upload_dir filter returns, only ever recurse into the plugin's own directory
		if ( 'hein-invoices' !== $name || ! is_dir( $dir ) ) {
			return;
		}

		$kept = false;

		foreach ( (array) scandir( $dir ) as $entry ) {
			if ( '.' === $entry || '..' === $entry ) {
				continue;
			}

			// Anything a filter or a backup tool nested here is not the plugin's to remove, and
			// rmdir() on a directory still holding it would only warn
			if ( ! is_file( $dir . $entry ) ) {
				$kept = true;
				continue;
			}

			wp_delete_file( $dir . $entry );
		}

		if ( ! $kept ) {
			// Core ships no rmdir wrapper, and WP_Filesystem can prompt for credentials, which an
			// uninstall running over a network of sites has no way to answer
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir
			rmdir( $dir );
		}
	}
}
