<?php

namespace Simplyin\Simplyin_W_Plugin\Sync;

use Simplyin\Simplyin_W_Plugin\Dto\Order\Order_DTO_Factory;
use Simplyin\Simplyin_W_Plugin\Dto\Order\Order_Items_DTO;
use WC_Data_Store;
use WC_Order;
use WC_Shipping_Zone;
use WP_Background_Process;

class SimplyIn_Background_Process extends WP_Background_Process {

	/**
	 * Max attempts for a 'create_order' queue item before it is dropped instead of retried again.
	 * WP_Background_Process removes an item from the queue as soon as task() returns
	 * false, so previously a single transient failure (rate limit, brief backend outage) meant the
	 * order was never synced again from this queue pass. 5 gives it a few passes to recover without
	 * looping forever on an order that is permanently unsyncable — and if it keeps failing, the next
	 * cron-driven Simplyin_Sync::sync_orders() run will still pick it back up (see there).
	 */
	const MAX_CREATE_ORDER_SYNC_ATTEMPTS = 5;

	/**
	 * @var string
	 */
	protected $action = 'simplyin_background_process';
	protected $simplyin_api;
	private ?array $update_merchant_setting_response = null;

	public function get_data() {
		return $this->data;
	}

	/**
	 * Push to queue
	 *
	 * @param mixed $data Data.
	 *
	 * @return $this
	 */
	public function push_to_queue( $data ) {
		$log_data = $data;
		if ( isset( $log_data['order'] ) ) {
			$log_data['order'] = 'hidden in debug log';
		}
		simplyin()->get_simplyin_logger()->log_if_wpdebug_on(
			sprintf( "[SimplyIn_Background_Process] [push_to_queue]  [data: %s]",
				wp_json_encode( $log_data ) ),
			"sync" );

		$this->data[] = $data;

		return $this;
	}


	/**
	 * Task
	 *
	 * Override this method to perform any actions required on each
	 * queue item. Return the modified item for further processing
	 * in the next pass through. Or, return false to remove the
	 * item from the queue.
	 *
	 * @param mixed $item Queue item to iterate over
	 *
	 * @return mixed
	 */

	public function setSimplyInApi( $simplyInApi ) {
		$this->simplyin_api = $simplyInApi;
	}


	protected function task( $item ) {
		if ( isset( $item['action'] ) ) {
			switch ( $item['action'] ) {
				case 'check_user':
					$this->checkUserExist( $item['user_email'],
						$item['user_id'] );
					break;
				case 'verify_user':
					$this->checkUserExist( $item['user_email'],
						$item['user_id'] );
					break;
				case 'export_order':
					$this->updateHistory( $item['user_id'] );
					break;
				case 'create_order':
					return $this->create_order_task( $item );
				case 'update_shipping_methods':
					$this->updateShippingMethods();
					break;
				case 'update_order_status':
					if ( false === $item['order'] instanceof WC_Order ) {
						return false;
					}
					$this->updateOrderStatus(
						$item['simplyin_order_id'],
						$item['order'],
						$item['status'],
						$item['old_status'] );

					break;
				case 'update_merchant_settings':
					$this->updateMerchantSetting();
					break;
			}
		}

		return false;
	}

	/**
	 * Run the 'create_order' queue item with bounded retry.
	 *
	 * WP_Background_Process keeps an item queued for another pass whenever task() returns anything
	 * other than false. So on a confirmed success (or a terminal, non-retryable outcome — see
	 * createOrder()) we return false to drop it; on an unconfirmed sync we return the item itself,
	 * with its attempt count incremented, so it gets tried again on the next pass. Once
	 * MAX_CREATE_ORDER_SYNC_ATTEMPTS is reached we give up and log a WARNING (visible without
	 * WP_DEBUG) so operators can see which order is stuck — and clear SimplyInOrderInQueue so the
	 * next Simplyin_Sync::sync_orders() cron pass is free to push a fresh attempt sequence for it
	 * (the accepted bounded-rescan tradeoff for an order that keeps failing every pass).
	 *
	 * @param array $item
	 *
	 * @return array|false
	 */
	private function create_order_task( array $item ) {
		$attempt   = ( isset( $item['sync_attempts'] ) ? (int) $item['sync_attempts'] : 0 ) + 1;
		$succeeded = $this->createOrder( (int) $item['order_id'], $attempt );

		if ( $succeeded ) {
			return false;
		}

		if ( $attempt >= self::MAX_CREATE_ORDER_SYNC_ATTEMPTS ) {
			simplyin()->get_woocommerce_logger( 'sync' )->log_warning(
				sprintf( "[SimplyIn_Background_Process] [create_order_task] [Giving up, order not synced] [attempts: %d] [order_id: %s]",
					$attempt,
					wp_json_encode( $item['order_id'] ) )
			);

			$order = $this->get_wc_order_instance( (int) $item['order_id'] );
			if ( null !== $order ) {
				$order->delete_meta_data( 'SimplyInOrderInQueue' );
				$order->save_meta_data();
			}

			return false;
		}

		$item['sync_attempts'] = $attempt;

		return $item;
	}

	/**
	 * Complete
	 *
	 * Override if applicable, but ensure that the below actions are
	 * performed, or, call parent::complete().
	 */
	protected function complete() {
		parent::complete();
	}


	/**
	 * Update merchant settings: GDPR, register by default, history length
	 */

	public function updateMerchantSetting() {
		$response = $this->simplyin_api->sendRequest( 'plugin/sync',
			'POST',
			Simplyin_Sync::get_plugin_info_to_sync_request(),
			[] );


		if ( $response && isset( $response->transactionHistoryLengthDays ) ) { // this means API responsed correctly

			// Redact the per-shop HMAC signing secret before logging the full response —
			// it is sensitive and must never hit debug logs.
			$response_for_log = clone $response;
			if ( isset( $response_for_log->paymentWebhookSigningSecret ) ) {
				$response_for_log->paymentWebhookSigningSecret = '***redacted***';
			}

			simplyin()->get_simplyin_logger()->log_if_wpdebug_on(
				sprintf( "[SimplyIn_Background_Process] [updateMerchantSetting] [Updating merchant settings] [response: %s]",
					wp_json_encode( $response_for_log ) ),
				"sync" );

			simplyin()->get_simplyin_logger()->log_if_wpdebug_on(
				sprintf( "[SimplyIn_Background_Process] [updateMerchantSetting] [Updating merchant settings] [lastChange: %s] [SimplyInSwitchLastChange: %s]",
					wp_json_encode( (int) $response->switchInfo->lastChange ),
					wp_json_encode( (int) get_option( 'SimplyInSwitchLastChange',
						true ) ) ),
				"sync" );

			if ( (int) $response->switchInfo->lastChange > (int) get_option( 'SimplyInSwitchLastChange',
					true ) ) {
				// only when isChecked is false or null, disable button in admin and switch of
				if ( ! $response->switchInfo->isChecked ) {
					$current_value = get_option( 'simplyin_register_by_default' );
					// Always backup before overwriting; use sentinel when option was never set.
					$backup_value = ( false === $current_value ) ? '__not_set__' : (string) $current_value;
					simplyin()->update_option( 'SimplyInRegisterByDefaultStateOld', $backup_value, 'sync' );
					simplyin()->update_option( 'SimplyInRegisterByDefaultDisabled', true, 'sync' );
					simplyin()->update_option( 'simplyin_register_by_default', 'off', 'sync' );
				} else {
					$was_disabled_by_api = '1' === (string) get_option( 'SimplyInRegisterByDefaultDisabled' );
					simplyin()->update_option( 'SimplyInRegisterByDefaultDisabled', false, 'sync' );

					if ( $was_disabled_by_api ) {
						$backup = get_option( 'SimplyInRegisterByDefaultStateOld' );

						if ( false === $backup || '__not_set__' === $backup ) {
							// Option was never set before API disabled — apply sensible default.
							simplyin()->update_option( 'simplyin_register_by_default', 'on', 'sync' );
						} else {
							// Restore merchant's previous setting; normalize legacy values.
							if ( '1' === $backup ) {
								$backup = 'on'; // Pre-5.5.29 slider format.
							} elseif ( '' === $backup ) {
								$backup = 'off'; // Normalize empty to explicit canonical value.
							}
							simplyin()->update_option( 'simplyin_register_by_default', $backup, 'sync' );
						}

						simplyin()->update_option( 'SimplyInRegisterByDefaultStateOld', '', 'sync' );
					}
				}
				simplyin()->update_option( 'SimplyInSwitchLastChange',
					$response->switchInfo->lastChange,
					'sync' );
			}
			simplyin()->update_option( 'SimplyInLogisticsPartnersOrder',
				$response->logisticsPartnersOrder->order,
				'sync' );
			if ( isset( $response->docsInfo ) && ! empty( $response->docsInfo ) && is_array( $response->docsInfo ) ) {
				$docs = [];
				foreach ( $response->docsInfo as $doc ) {
					if ( isset( $doc->key ) ) {
						$docs[ sanitize_key( $doc->key ) ] = $doc->url;
					}
				}
				simplyin()->update_option( 'SimplyInDocs',
					$docs,
					'sync' );

			}

			// HMAC payment-webhook signing secret (per shop), delivered by the backend.
			// Shared with the reachable API-key-save path via one helper so the secret is persisted
			// identically everywhere (Model B: absent=no change, empty=rotate/clear, set=store
			// encrypted at rest).
			Simplyin_Sync::persist_webhook_signing_secret( $response );

		}
	}

	/**
	 * Check whether a Simply.IN account exists for this e-mail, and mark the WP user if given.
	 *
	 * @param string   $user_email
	 * @param int|null $user_id
	 *
	 * @return bool|null True/false is a CONFIRMED answer (account exists / does not exist) —
	 *                    the API call itself succeeded. Null means the check itself failed
	 *                    (network error, malformed response) and is NOT a confirmed answer —
	 *                    callers must not treat null the same as a confirmed false, or a
	 *                    transient API blip gets permanently mistaken for "no account".
	 */
	protected function checkUserExist( $user_email, $user_id = null ) {
		$bodyData = [ "data" => [ hash( 'sha256', $user_email ) ] ];
		$response = $this->simplyin_api->sendRequest( 'users/verifyHashedEmailsExist',
			'POST',
			$bodyData );

		if ( ! is_object( $response ) || empty( $response->ok ) ) {
			return null;
		}

		if ( empty( $response->data ) || ! isset( $response->data[0] ) ) {
			if ( $user_id ) {
				update_user_meta( $user_id, 'SimplyInUserExist', 0 );
			}

			return false;
		} else {
			if ( $response->data[0]->email == hash( 'sha256', $user_email ) ) {
				if ( $user_id ) {
					update_user_meta( $user_id, 'SimplyInUserExist', 1 );
					update_user_meta( $user_id,
						'SimplyInUserHmacEmail',
						$response->data[0]->hmacEmail );
					update_user_meta( $user_id,
						'SimplyInUserStatus',
						$response->data[0]->status );
				}

				return true;
			} else {
				if ( $user_id ) {
					update_user_meta( $user_id, 'SimplyInUserExist', 0 );
				}

				return false;
			}
		}
	}

	protected function updateHistory( $user_id ) {

	}

	protected function updateOrderStatus(
		$simplyin_order_id,
		WC_Order $order,
		$status,
		$old_status
	) {
		simplyin()
			->get_simplyin_logger()
			->log_if_wpdebug_on( sprintf( "[SimplyIn_Background_Process] [updateOrderStatus] [Background task] %s",
				wp_json_encode( [
					'simplyin_order_id' => $simplyin_order_id,
					'order_id'          => $order->get_id(),
					'old status'        => $old_status,
					'new status'        => $status,
				] )

			),
				"sync" );

		simplyin()
			->get_api()
			->simplyInOnOrderUpdate(
				$old_status,
				$status,
				$order );

	}

	protected function updateShippingMethods() {
		$methods                = array_values( $this->get_all_shipping_methods() );
		$body_data['providers'] = $methods;
		$response               = $this->simplyin_api->sendRequest( 'merchants/setLogisticsProviders',
			'POST',
			$body_data );
	}

	private function get_wc_order_instance( int $order_id ): ?WC_Order {
		$order = wc_get_order( $order_id );

		if ( false === $order instanceof WC_Order ) {
			$type = 'bool';
			if ( $order instanceof \WC_Order_Refund ) {
				$type = 'WC_Order_Refund';
			}

			simplyin()->get_simplyin_logger()->log_if_wpdebug_on(
				sprintf( "[SimplyIn_Background_Process] [createOrder] [get_wc_order_instance] [%s is not instance of WC_ORDER] [ignoring]",
					wp_json_encode( $type ) ),
				"sync" );

			return null;

		}

		return $order;
	}

	/**
	 * @param mixed $order_id
	 * @param int   $attempt 1-based attempt number, for logging.
	 *
	 * @return bool True drops the item from THIS background-process pass — either because the
	 *              order is confirmed synced (createdOrder / already-exists response), already
	 *              exported, or because checkUserExist() CONFIRMED no Simply.IN account for this
	 *              e-mail (marked SimplyInOrderNotApplicable; permanently skipped from every
	 *              future Simplyin_Sync::sync_orders() pass — NOT re-checked again). False means
	 *              no confirmed result was reached: either the checkout/createOrder API call, or
	 *              the checkUserExist() check itself, failed to confirm — create_order_task()
	 *              retries that up to MAX_CREATE_ORDER_SYNC_ATTEMPTS before giving up (at which
	 *              point the order IS picked up again by the next sync_orders() pass).
	 */
	protected function createOrder( $order_id, int $attempt = 1 ): bool {
		$order = $this->get_wc_order_instance( (int) $order_id );

		if ( null === $order ) {
			return true;
		}

		if ( 1 === (int) $order->get_meta( 'SimplyInOrderExported', true ) ) {
			$order->delete_meta_data( 'SimplyInOrderInQueue' );
			$order->save_meta_data();

			return true;
		}

		/**
		 * MISSING -> subtotal_amount
		 * MISSING -> discount_amount
		 * MISSING -> tax_amount
		 */
		$user_email = $order->get_billing_email();
		$synced     = true;
		$member     = $this->checkUserExist( $user_email );
		// if user in SimplyIn create order for him
		if ( true === $member ) {
			$plugin_version      = SimplyInGetPluginVersion();
			$woocommerce_version = get_option( 'woocommerce_version' );
			$payment_method_data = SimplyInGetPaymentMethodData( $order );
			$body_data           = Order_DTO_Factory::build_create_order_body_data_dto(
				$order,
				$payment_method_data,
				$plugin_version,
				$woocommerce_version,
				'',
				'',
				'',
				'',
				'',
				'',
				$user_email
			)->toArray();


			$response = $this->simplyin_api->sendRequest( 'checkout/createOrder',
				"POST",
				$body_data );
			if ( $response && isset( $response->createdOrder )
				&& SimplyIn_Api_Order_Response::created_order_payload_is_for_this_wc_order( $response->createdOrder,
					$order ) ) {
				$order->update_meta_data( 'SimplyInOrderId',
					$response->createdOrder->_id );
				$order->update_meta_data( 'SimplyInOrderExported', 1 );

				simplyin()->get_simplyin_logger()->log_if_wpdebug_on(
					sprintf( "[SimplyIn_Background_Process] [createOrder] Sync order success. [orderId: %s]",
						wp_json_encode( $order_id ) ),
					"sync" );

			} elseif ( SimplyIn_Api_Order_Response::response_indicates_order_already_exists( $response ) ) {
				$order->update_meta_data( 'SimplyInOrderExported', 1 );

				simplyin()->get_simplyin_logger()->log_if_wpdebug_on(
					sprintf( "[SimplyIn_Background_Process] [createOrder] [Failed]  [message: %s] [order_id: %s] [user_email: %s]",
						wp_json_encode( is_object( $response ) && isset( $response->message ) ? $response->message : null ),
						wp_json_encode( $order_id ),
						wp_json_encode( $user_email ),
					),
					"sync" );

			} else {
				$synced = false;

				simplyin()->get_simplyin_logger()->log_if_wpdebug_on(
					sprintf( "[SimplyIn_Background_Process] [createOrder] [Failed]  [message: %s] [order_id: %s] [user_email: %s]",
						wp_json_encode( is_object( $response ) && isset( $response->message ) ? $response->message : null ),
						wp_json_encode( $order_id ),
						wp_json_encode( $user_email ),
					),
					"sync" );

				// WARNING (not debug-gated) so operators can see WHY an order is stuck without
				// needing WP_DEBUG on — the debug log above is invisible on most production shops.
				// Don't claim "will retry" on the final attempt — create_order_task()
				// logs its own "giving up" WARNING immediately after this one in that case.
				simplyin()->get_woocommerce_logger( 'sync' )->log_warning(
					sprintf( "[SimplyIn_Background_Process] [createOrder] [attempt %d/%d] Sync failed%s. [order_id: %s] [response: %s]",
						$attempt,
						self::MAX_CREATE_ORDER_SYNC_ATTEMPTS,
						$attempt < self::MAX_CREATE_ORDER_SYNC_ATTEMPTS ? ', will retry' : ' (final attempt)',
						wp_json_encode( $order_id ),
						wp_json_encode( $response ) )
				);
			}
		} elseif ( false === $member ) {
			// Terminal, not a transient failure: checkUserExist() CONFIRMED this e-mail has no
			// Simply.IN account, so this order will NEVER be exported. Mark it distinctly from
			// SimplyInOrderExported (which means "confirmed synced") so Simplyin_Sync::sync_orders()'s
			// watermark guard can tell the two apart and stop treating it as unconfirmed —
			// previously this held the watermark back on it, and re-queued it, forever.
			$order->update_meta_data( 'SimplyInOrderNotApplicable', 1 );

			simplyin()->get_simplyin_logger()->log_if_wpdebug_on(
				sprintf( "[SimplyIn_Background_Process] [createOrder] checkUserExist returns false. [email: %s] [orderId: %s]",
					wp_json_encode( $user_email ),
					wp_json_encode( $order_id ) ),
				"sync" );
		} else {
			// $member === null: checkUserExist() itself did not get a confirmed answer (network
			// error, malformed response) — a transient condition, NOT a confirmed non-member.
			// Treat as an unconfirmed sync (like an API createOrder() failure below) so
			// create_order_task() retries it, instead of a transient blip permanently masquerading
			// as "no account" and silently dropping an otherwise-syncable order forever.
			$synced = false;

			simplyin()->get_simplyin_logger()->log_if_wpdebug_on(
				sprintf( "[SimplyIn_Background_Process] [createOrder] checkUserExist check failed (no confirmed answer). [email: %s] [orderId: %s]",
					wp_json_encode( $user_email ),
					wp_json_encode( $order_id ) ),
				"sync" );

			simplyin()->get_woocommerce_logger( 'sync' )->log_warning(
				sprintf( "[SimplyIn_Background_Process] [createOrder] [attempt %d/%d] checkUserExist check failed%s. [order_id: %s]",
					$attempt,
					self::MAX_CREATE_ORDER_SYNC_ATTEMPTS,
					$attempt < self::MAX_CREATE_ORDER_SYNC_ATTEMPTS ? ', will retry' : ' (final attempt)',
					wp_json_encode( $order_id ) )
			);
		}

		// SimplyInOrderInQueue marks "still owned by an in-flight background-process attempt
		// sequence" for Simplyin_Sync::sync_orders()'s dedup guard. Only clear it once this order
		// has reached a terminal outcome (confirmed synced or confirmed not-applicable) — on a
		// retryable failure ($synced === false) it must stay set, otherwise sync_orders() would
		// push a duplicate item while create_order_task() is still retrying this one.
		if ( $synced ) {
			$order->delete_meta_data( 'SimplyInOrderInQueue' );
		}
		$order->save_meta_data();

		return $synced;
	}


	protected function get_all_shipping_methods() {
		$methods    = [];
		$methodsIds = [];
		$data_store = WC_Data_Store::load( 'shipping-zone' );
		$raw_zones  = $data_store->get_zones();
		foreach ( $raw_zones as $raw_zone ) {
			$zone                  = new WC_Shipping_Zone( $raw_zone );
			$zone_shipping_methods = $zone->get_shipping_methods();
			foreach ( $zone_shipping_methods as $index => $method ) {
				if ( $method->is_enabled() ) {
					$methodsIds[]                      = $method->get_rate_id();
					$methods[ $method->get_rate_id() ] = $method->get_method_title();
				}
			}
		}

		return $methods;
	}
}
