<?php

declare ( strict_types=1 );

namespace Simplyin\Simplyin_W_Plugin;

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

use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry;
use Exception;
use Simplyin\Simplyin_W_Plugin\Controller\Ajax\Widget\Widget_Controller;
use Simplyin\Simplyin_W_Plugin\Helper\Secret_Cipher;
use Simplyin\Simplyin_W_Plugin\Lib\Base_Plugin\Abstract_Base_Plugin;
use Simplyin\Simplyin_W_Plugin\Lib\Base_Plugin\Logger\Logger_Interface;
use Simplyin\Simplyin_W_Plugin\Lib\Base_Plugin\Logger\Null_Logger;
use Simplyin\Simplyin_W_Plugin\Lib\Base_Plugin\Woocommerce_Logger;
use Simplyin\Simplyin_W_Plugin\Lib\Remote\Sin_Core_Api\Plugin_Rsa_Registrar;
use Simplyin\Simplyin_W_Plugin\Lib\Remote\Sin_Core_Api\Plugin_Rsa_Signer;
use Simplyin\Simplyin_W_Plugin\Lib\Remote\Sin_Core_Api\Simplyin_Api_Legacy_Client;
use Simplyin\Simplyin_W_Plugin\Lib\Session\Simplyin_Session;
use Simplyin\Simplyin_W_Plugin\Sync\Simplyin_Sync;
use Simplyin\Simplyin_W_Plugin\Payments\Ajax\Blik\Blik_Ajax_Handler;
use Simplyin\Simplyin_W_Plugin\Payments\Ajax\Card\Card_Ajax_Handler;
use Simplyin\Simplyin_W_Plugin\Payments\Gateway\Payment_Fields\Payment_Fields;
use Simplyin\Simplyin_W_Plugin\Payments\Gateway\Woocommerce_Blocks\Payment_Blocks_Support;

class Plugin extends Abstract_Base_Plugin {


	public const SHORT_SLUG = 'simplyin';

	/**
	 * Version marker for the last intentional force of the checkout checkbox default.
	 *
	 * Do not bump this together with the plugin version. Changing this value causes
	 * the plugin to overwrite merchant configuration again and force
	 * simplyin_register_by_default = 'on'.
	 *
	 * Update only after an explicit business decision to repeat the force migration.
	 */
	private const FORCE_REGISTER_BY_DEFAULT_ON_VERSION = '5.5.38';

	/**
	 * One-off cron hook that runs RSA auto-registration off the request path. Public so
	 * the API-key-save path (Simplyin_Sync) can queue it too, keeping the hook name a single source
	 * of truth.
	 */
	public const RSA_AUTO_REGISTER_HOOK = 'simplyin_rsa_auto_register';

	/**
	 * Recurring cron hook that keeps retrying RSA auto-registration for a connected-but-unregistered
	 * shop, so a shop already on the current plugin version migrates once the backend enables RSA —
	 * without waiting for the next plugin update (the one-off hook only fires on a version bump).
	 * Self-terminating: the handler clears it once the shop is registered.
	 */
	private const RSA_AUTO_REGISTER_CRON = 'simplyin_rsa_auto_register_cron';

	private static ?Legacy_App $app = null;

	public static bool $logger_enabled = false;
	private static array $simplyin_config = [];
	private static array $locked_options = [];

	/**
	 * Assets helper.
	 *
	 * @var Assets|null
	 */
	private ?Assets $assets = null;

	/**
	 * Get assets helper.
	 *
	 * @return Assets
	 * @throws Exception When plugin file cannot be resolved.
	 */
	public function assets(): Assets {
		if ( null === $this->assets ) {
			$this->assets = new Assets( $this->get__file__() );
		}

		return $this->assets;
	}

	/**
	 * Before init hook.
	 *
	 * @return void
	 * @throws Exception When config is not available yet.
	 */
	protected function before_init(): void {
		$this->declare_hpos_compatibility();
		// Bind the one-off RSA auto-registration handler unconditionally so the scheduled
		// event (queued by the migration) has its callback registered when wp-cron fires it.
		add_action( self::RSA_AUTO_REGISTER_HOOK, [ $this, 'run_rsa_auto_register' ] );
		add_action( self::RSA_AUTO_REGISTER_CRON, [ $this, 'run_rsa_auto_register_cron' ] );
		// Piggyback the webhook-secret refresh on the same off-request one-off event so a
		// fully-automatic (cron-driven) update also pulls the signing secret without an admin visit.
		add_action( self::RSA_AUTO_REGISTER_HOOK, [ $this, 'run_webhook_secret_sync' ] );
		// Register RSA right after a plugin update completes — works for BOTH manual and fully
		// automatic (cron-driven, no admin navigation) updates, so registration does not depend on
		// someone opening wp-admin. Fires in whatever process ran the update (admin or wp-cron).
		add_action( 'upgrader_process_complete', [ $this, 'on_plugin_upgraded' ], 10, 2 );
		$this->before_init_when_api_connected();
	}

	/**
	 * After WordPress updates plugins, if this plugin was among them, kick RSA auto-registration in
	 * the same process (no admin page load needed). Queues the one-off cron event as a safety net so
	 * a fresh-code run picks it up even if the inline attempt (old code) is a no-op. Best-effort.
	 *
	 * @param mixed $upgrader   WP_Upgrader instance (unused).
	 * @param array $hook_extra Update context: action/type/plugins.
	 */
	public function on_plugin_upgraded( $upgrader, $hook_extra ): void {
		unset( $upgrader );
		if ( ! is_array( $hook_extra )
			|| ( $hook_extra['action'] ?? '' ) !== 'update'
			|| ( $hook_extra['type'] ?? '' ) !== 'plugin' ) {
			return;
		}
		$ours    = plugin_basename( $this->get__file__() );
		$updated = (array) ( $hook_extra['plugins'] ?? [] );
		if ( ! in_array( $ours, $updated, true ) ) {
			return;
		}
		if ( ! wp_next_scheduled( self::RSA_AUTO_REGISTER_HOOK ) ) {
			// Safety-net event only — the inline call right below already does the real work in
			// this same process, so no spawn_cron() here: racing the loopback against
			// the inline run_rsa_auto_register() would let two processes both see "not registered"
			// and both start a fresh key-pair registration (auto_register() has no lock, only a
			// TOCTOU is_registered() check).
			wp_schedule_single_event( time(), self::RSA_AUTO_REGISTER_HOOK );
		}
		$this->run_rsa_auto_register();
		// Pull the HMAC webhook signing secret in the same update hook — for BOTH manual and fully
		// automatic updates, without needing the merchant to open wp-admin or re-save the API key.
		$this->run_webhook_secret_sync();
	}

	/**
	 * Best-effort, never-fatal webhook signing-secret refresh, delegating to the shared, throttled
	 * Simplyin_Sync helper. Bound to the plugin-update triggers (upgrader_process_complete + the
	 * one-off event) so a shop stores its secret without re-saving the API key — mirrors the RSA
	 * auto-registration triggers. The admin backstop calls the same helper from admin_init.
	 */
	public function run_webhook_secret_sync(): void {
		try {
			Simplyin_Sync::sync_webhook_secret_now();
		} catch ( \Throwable $e ) {
			simplyin()
				->get_simplyin_logger()
				->log_if_wpdebug_on( '[Plugin] run_webhook_secret_sync failed — ' . $e->getMessage(), 'api-call' );
		}
	}

	private function declare_hpos_compatibility(): void {
		add_action(
			'before_woocommerce_init',
			function () {
				if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) {
					\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility(
						'custom_order_tables',
						$this->get__file__(),
						true
					);
				}
			}
		);
	}

	private function before_init_when_api_connected() {
		if ( (string) get_option( 'SimplyInApiConnection' ) === '1' ) {
			$this->ensure_rsa_auto_register_cron();
			( new Widget_Controller() )->register_ajax_hooks();
			$this->init_payment_gateway();

			add_action( 'woocommerce_blocks_loaded',
				[ $this, 'woocommerce_block_support' ] );


			$blik_ajax_handler = new Blik_Ajax_Handler();
			$blik_ajax_handler->register_ajax_hooks();
			$card_ajax_handler = new Card_Ajax_Handler();
			$card_ajax_handler->register_ajax_hooks();

		}
	}

	protected function plugins_loaded_hooks(): void {
		$this->run_db_migrations();
	}

	private function run_db_migrations(): void {
		$current_version  = function_exists( 'SimplyInGetPluginVersion' ) ? SimplyInGetPluginVersion() : '';
		$migrated_version = get_option( 'simplyin_db_version', '' );

		if ( $migrated_version === $current_version || '' === $current_version ) {
			return;
		}

		$this->migrate_register_by_default();
		$this->migrate_force_register_by_default_on();
		$this->migrate_encrypt_secrets_at_rest();
		self::schedule_rsa_auto_register();

		update_option( 'simplyin_db_version', $current_version );
	}

	/**
	 * One-time re-encryption of plugin secrets at rest: on upgrade, seal any
	 * still-plaintext RSA private key / webhook signing secret with Secret_Cipher. Idempotent
	 * (already-encrypted values are skipped) and safe when no key material is available —
	 * Secret_Cipher::encrypt() then returns the value unchanged, so we only rewrite on a real seal.
	 */
	private function migrate_encrypt_secrets_at_rest(): void {
		$options = [
			'simplyin_plugin_rsa_private_key',
			'simplyin_plugin_payment_webhook_secret',
		];
		foreach ( $options as $option ) {
			$stored = (string) get_option( $option, '' );
			if ( '' === $stored || Secret_Cipher::is_encrypted( $stored ) ) {
				continue;
			}
			// Re-store with autoload disabled even when encryption is unavailable (encrypt()
			// returns the plaintext unchanged) — a secret option must never remain autoloaded,
			// regardless of whether it could be sealed.
			update_option( $option, Secret_Cipher::encrypt( $stored ), false );
		}
	}

	/**
	 * Shared zero-touch RSA auto-registration scheduler: when the
	 * API is connected and the shop is not yet paired, queue the one-shot auto-registration event
	 * (the shop self-registers its RSA key using the stored API key — no Merchant Panel step).
	 * Idempotent + best-effort: a no-op once registered, re-queued while still unregistered. Single
	 * call site for the gate + schedule so the version-bump migration (run_db_migrations) and the
	 * API-key-save path (Simplyin_Sync::simplyin_update_merchand_settings) can't drift.
	 *
	 * The actual work (two synchronous HTTP calls) runs off the request path via the one-off
	 * scheduled event, so the request that queues it is never frozen waiting on the backend (which
	 * would risk max_execution_time on shared hosting). The handler is run_rsa_auto_register(),
	 * bound in before_init().
	 *
	 * Explicitly spawns wp-cron right after scheduling, instead of relying on whichever
	 * request happens to trigger this — WordPress only self-pings wp-cron.php from the normal
	 * front-end `wp()` bootstrap, which a REST/AJAX call or a WP-CLI-driven request does not always
	 * go through. NOTE: spawn_cron() itself no-ops while DOING_CRON is already true, so this does
	 * NOT help when this method first runs from inside a later wp-cron.php tick with no real visit
	 * in between (rare — most shops get a real front-end hit shortly after any update); that case
	 * still falls back to the existing daily ensure_rsa_auto_register_cron() backstop.
	 *
	 * The spawn_cron() call ONLY (not the scheduling above it) is skipped under WP-CLI: its
	 * wp-cron.php loopback ping is meaningless there (no front-end request triggered this), and
	 * hit a WP-CLI-specific bootstrap-order issue where spawn_cron() fatals on an undefined
	 * WP_CRON_LOCK_TIMEOUT even though core defines it unconditionally and early —
	 * reproducible only via WP-CLI, never via a real request. The event itself is still queued
	 * either way; it fires from the next real front-end hit, or the daily backstop above.
	 */
	public static function schedule_rsa_auto_register(): void {
		if ( (string) get_option( 'SimplyInApiConnection' ) !== '1' ) {
			return;
		}
		$signer = new Plugin_Rsa_Signer();
		if ( $signer->is_registered() || $signer->is_disabled() ) {
			// Already paired, or RSA disabled locally (kill switch) — don't queue a redundant
			// event and don't re-enable a deliberately disabled shop.
			return;
		}
		if ( ! wp_next_scheduled( self::RSA_AUTO_REGISTER_HOOK ) ) {
			wp_schedule_single_event( time(), self::RSA_AUTO_REGISTER_HOOK );
			if ( ! defined( 'WP_CLI' ) || ! WP_CLI ) {
				spawn_cron();
			}
		}
	}

	/**
	 * Scheduled (one-off) RSA auto-registration handler. Runs off the request path. Best-effort
	 * and never fatal: key generation (OpenSSL/CSPRNG) or the HTTP client can throw — swallow
	 * any Throwable and log, so a failed attempt is simply retried on the next version bump.
	 */
	public function run_rsa_auto_register(): void {
		if ( (string) get_option( 'SimplyInApiConnection' ) !== '1' ) {
			return;
		}
		if ( ( new Plugin_Rsa_Signer() )->is_disabled() ) {
			return; // RSA kill switch engaged — do not auto re-register.
		}
		try {
			( new Plugin_Rsa_Registrar() )->auto_register();
		} catch ( \Throwable $e ) {
			simplyin()
				->get_simplyin_logger()
				->log_if_wpdebug_on( '[Plugin] run_rsa_auto_register failed — ' . $e->getMessage(), 'api-call' );
		}
	}

	/**
	 * Recurring RSA auto-registration tick. Reuses run_rsa_auto_register() (connection-gated,
	 * best-effort, never fatal) and self-terminates the recurring event once the shop is
	 * registered, so it stops retrying after a successful migration.
	 */
	public function run_rsa_auto_register_cron(): void {
		$signer = new Plugin_Rsa_Signer();
		if ( $signer->is_registered() || $signer->is_disabled() ) {
			// Migrated, or RSA disabled (kill switch) — stop the recurring check.
			wp_clear_scheduled_hook( self::RSA_AUTO_REGISTER_CRON );

			return;
		}
		$this->run_rsa_auto_register();
	}

	/**
	 * Keep a daily RSA auto-registration check scheduled for a connected-but-unregistered shop,
	 * and clear it once registered. This lets a shop already on the current plugin version pick up
	 * RSA after the backend flag is switched on, without waiting for a plugin update. Called from
	 * the API-connected branch of before_init(); the one-off version-bump path still handles the
	 * fast case on plugin update.
	 */
	private function ensure_rsa_auto_register_cron(): void {
		$scheduled = (bool) wp_next_scheduled( self::RSA_AUTO_REGISTER_CRON );
		$signer    = new Plugin_Rsa_Signer();

		// Once paired, or if RSA is disabled locally (kill switch), stop the recurring check so a
		// deliberately disabled shop is never put back onto the auto-register loop.
		if ( $signer->is_registered() || $signer->is_disabled() ) {
			if ( $scheduled ) {
				wp_clear_scheduled_hook( self::RSA_AUTO_REGISTER_CRON );
			}

			return;
		}

		if ( ! $scheduled ) {
			wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', self::RSA_AUTO_REGISTER_CRON );
		}
	}

	/**
	 * Clear plugin-scheduled cron events on deactivation. No return type — must match
	 * Abstract_Base_Plugin::plugin_deactivate_actions() (untyped) or PHP fatals on load.
	 */
	public function plugin_deactivate_actions() {
		wp_clear_scheduled_hook( self::RSA_AUTO_REGISTER_HOOK );
		wp_clear_scheduled_hook( self::RSA_AUTO_REGISTER_CRON );
	}

	private function migrate_force_register_by_default_on(): void {
		$applied_version = (string) get_option(
			'simplyin_register_by_default_force_applied_version',
			''
		);

		$already_applied = '' !== $applied_version
		                   && version_compare( $applied_version, self::FORCE_REGISTER_BY_DEFAULT_ON_VERSION, '>=' );

		if ( ! $already_applied ) {
			update_option( 'simplyin_register_by_default', 'on' );
			update_option( 'SimplyInRegisterByDefaultDisabled', false );
			update_option( 'SimplyInRegisterByDefaultStateOld', '' );
			update_option(
				'simplyin_register_by_default_force_applied_version',
				self::FORCE_REGISTER_BY_DEFAULT_ON_VERSION
			);
		}

		delete_option( 'simplyin_migration_force_register_on_5_5_37_done' );
	}

	private function migrate_register_by_default(): void {
		$value = get_option( 'simplyin_register_by_default' );

		if ( false === $value ) {
			return;
		}

		// Normalize pre-5.5.29 slider value ('1') to canonical 'on'.
		if ( '1' === $value ) {
			if ( false === get_option( 'simplyin_register_by_default_legacy' ) ) {
				update_option( 'simplyin_register_by_default_legacy', $value );
			}
			update_option( 'simplyin_register_by_default', 'on' );
			return;
		}

		// Normalize empty string (ambiguous legacy state) to explicit canonical 'off'.
		if ( '' === $value ) {
			if ( false === get_option( 'simplyin_register_by_default_legacy' ) ) {
				update_option( 'simplyin_register_by_default_legacy', $value );
			}
			update_option( 'simplyin_register_by_default', 'off' );
		}
	}

	/**
	 * @throws Exception
	 */
	public function enqueue_frontend_scripts(): void {
		if ( ! get_option( 'SimplyInApiConnection' ) ) {
			return;
		}

		$assets = $this->assets();

		if ( ( is_checkout() && ! has_block( 'woocommerce/checkout' ) ) || is_checkout_pay_page() ) {
			$assets->enqueue_script(
				'SimplyInGatewayJs',
				'public/gateway/front.js',
				[ 'jquery' ],
				true
			);
		}

		if ( is_account_page() && ! is_user_logged_in() ) {
			simplyin_load_react_app_for_myaccount( $assets );
		}

		$assets->enqueue_style(
			'SimplyInGatewayCss',
			'public/gateway/front.css',
			[],
			'all'
		);

		$assets->enqueue_style(
			'SimplyInThankYouPageCss',
			'public/thankyoupage/status/status.css',
			[],
			'all'
		);

		/**
		 * Allow custom frontend assets.
		 *
		 * @param Assets $assets Assets helper.
		 */
		do_action( 'simplyin_enqueue_frontend_scripts', $assets );
	}


	public function enqueue_dashboard_scripts() {
		$assets = $this->assets();

		$assets->enqueue_script(
			'SimplyInAdminJs',
			'public/admin/admin.js',
			[ 'jquery' ],
			true
		);

		$assets->enqueue_style(
			'SimplyInAdminCss',
			'public/admin/admin.css',
			[],
			'all'
		);

		$assets->enqueue_style(
			'SimplyInGatewayOptionsCss',
			'public/admin/gateway_options.css',
			[],
			'all'
		);
	}

	public function init(): void {


	}

	public function get_woocommerce_logger( ?string $log_id = null
	): Logger_Interface {

		$log_id = $log_id
			? $this->prefix_by_short_slug( $log_id )
			: $this->get_from_config( 'slug' );

		if ( self::$logger_enabled ) {
			$logger = new Woocommerce_Logger( $log_id );
		} else {
			$logger = new Null_Logger();
		}

		return $logger;
	}

	public function get_simplyin_logger(): Simplyin_Logger {
		return new Simplyin_Logger();
	}

	public function prefix_by_short_slug( string $string ): string {
		return self::SHORT_SLUG . '_' . $string;
	}


	public function get_app(): Legacy_App {
		if ( ! self::$app ) {
			self::$app = new Legacy_App();
		}

		return self::$app;
	}

	public function update_option(
		$key,
		$value,
		?string $context = null,
		$locked = false
	) {
		if ( key_exists( $key, self::$locked_options ) ) {
			$this->get_woocommerce_logger( $context )->log_debug(
				sprintf( "[update_option] [key: %s] [locked!]",
					$key ) );

			return;
		}

		if ( $locked ) {
			self::$locked_options[ $key ] = true;
		}

		$this->get_woocommerce_logger( $context )->log_debug(
			sprintf( "[update_option] [key: %s] [value: %s]",
				$key,
				wp_json_encode( $value ) ) );


		update_option( $key, $value );
	}

	public function get_api(): Simplyin_Api_Legacy_Client {
		return new Simplyin_Api_Legacy_Client( 'simplyin',
			SimplyInGetPluginVersion() );
	}

	public function get_simplyin_config(): array {

		return self::$simplyin_config;
	}

	public function set_simplyin_config( array $simplyin_config ): void {
		self::$simplyin_config = $simplyin_config;
	}

	public function get_backend_simplyin_url(): string {
		$simplyin_config = $this->get_simplyin_config();

		if ( isset( $simplyin_config['backendSimplyIn'] ) ) {
			return $simplyin_config['backendSimplyIn'];
		} else {
			return '';
		}
	}

	public function get_environment_version_prefix(): string {
		$simplyin_config = $this->get_simplyin_config();

		if ( isset( $simplyin_config['environmentVersion'] ) ) {
			return $simplyin_config['environmentVersion'];
		} else {
			return '';
		}
	}

	public function get_simplyin_session(): Simplyin_Session {
		return new Simplyin_Session();
	}

	private function init_payment_gateway() {
		add_filter( 'woocommerce_payment_gateways',
			function ( $gateways ) {
				$gateways[]
					= 'Simplyin\Simplyin_W_Plugin\Payments\Gateway\Simplyin_Gateway';

				return $gateways;
			}
		);
	}

	public function woocommerce_block_support() {
		$host = $request_uri = '';
		if ( isset( $_SERVER['HTTP_HOST'] ) ) {
			$host = esc_url_raw( wp_unslash( $_SERVER['HTTP_HOST'] ) );
		}

		if ( isset( $_SERVER['REQUEST_URI'] ) ) {
			$request_uri = esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) );
		}

		$current_url = $host . $request_uri;

		$search_phrase = "order-received";
		if ( strpos( $current_url, $search_phrase ) !== false ) {
			return;
		}

		if ( class_exists( 'Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType' ) ) {
			add_action(
				'woocommerce_blocks_payment_method_type_registration',
				function ( PaymentMethodRegistry $payment_method_registry ) {
					$payment_method_registry->register( new Payment_Blocks_Support() );
				}
			);
		}
	}

	public function handle_api_unauthorized(): void {
		$this->update_option( 'SimplyInSettingsErrorMsg',
			__( 'Error connecting to Simply.IN - check the API key.',
				'simplyin' ),
			null,
			true );

		$this->update_option( 'SimplyInApiConnection',
			0,
			'sync' );
		$this->update_option( 'SimplyInApiConnectedLastTry',
			time(),
			'sync' );

		$simplyin_session = $this->get_simplyin_session();
		$previous_token = (string) $simplyin_session->get( 'simplyin_auth_token', '' );
		$previous_session_id = (string) $simplyin_session->get( 'id', '' );

		$simplyin_session->clear();
		Payment_Fields::clear_methods_cache( $previous_token, $previous_session_id );

		$simplyin_session->set( 'simplyin_user_data_commerce', null );
		$simplyin_session->set(
			'simplyin_user_data_commerce_timestamp',
			null
		);
	}

}
