<?php
/*
 * Copyright (c) 2024 LatePoint LLC. All rights reserved.
 */

class OsTransactionIntentModel extends OsModel {
	var $id,
		$intent_key,
		$order_id,
		$customer_id,
		$invoice_id,
		$transaction_id,
		$payment_data = '',
		$payment_data_arr,
		$other_data,
		$charge_amount,
		$specs_charge_amount,
		$status,
		$order_form_page_url,
		$updated_at,
		$created_at;

	function __construct( $id = false ) {
		parent::__construct();
		$this->table_name = LATEPOINT_TABLE_TRANSACTION_INTENTS;

		if ( $id ) {
			$this->load_by_id( $id );
		}
	}

	public function calculate_specs_charge_amount() {
		/**
		 * Convert transaction intent charge amount to specs
		 *
		 * @param {string} $charge_amount original charge amount of a transaction intent
		 * @param {OsTransactionIntentModel} $transaction_intent transaction intent model
		 *
		 * @returns {string} The filtered to specs charge amount
		 *
		 * @since 5.1.3
		 * @hook latepoint_transaction_intent_specs_charge_amount
		 *
		 */
		$this->specs_charge_amount = apply_filters( 'latepoint_transaction_intent_specs_charge_amount', $this->charge_amount, $this );
	}

	protected function params_to_sanitize() {
		return [
			'charge_amount' => 'money',
		];
	}


	public function get_payment_data_value( string $key ): string {
		if ( ! isset( $this->payment_data_arr ) ) {
			$this->payment_data_arr = json_decode( $this->payment_data, true );
		}

		return $this->payment_data_arr[ $key ] ?? '';
	}

	public function set_payment_data_value( string $key, string $value, bool $save = true ) {
		$this->payment_data_arr         = json_decode( $this->payment_data, true );
		$this->payment_data_arr[ $key ] = $value;
		$this->payment_data             = wp_json_encode( $this->payment_data_arr );
		if ( $save ) {
			$this->update_attributes( [ 'payment_data' => $this->payment_data ] );
		}
	}


	public function is_processing(): bool {
		return $this->status == LATEPOINT_TRANSACTION_INTENT_STATUS_PROCESSING;
	}
	public function is_failed(): bool {
		return $this->status == LATEPOINT_TRANSACTION_INTENT_STATUS_FAILED;
	}

	public function wait_for_transaction_completion(): OsTransactionIntentModel {
		$attempts      = 0;
		$max_attempts  = 6;
		$delay_seconds = 2;

		while ( $attempts < $max_attempts ) {
			if ( ! $this->is_processing() ) {
				return $this;
			}
			sleep( $delay_seconds );
			$attempts++;
			$this->load_by_id( $this->id );
		}
		if ( $this->is_processing() ) {
			// if it's still processing after waiting - mark as failed
			$this->mark_as_failed();
		}
		return $this;
	}

	public function convert_to_transaction() {
		if ( $this->is_processing() ) {
			$this->wait_for_transaction_completion();
			if ( $this->is_failed() ) {
				$this->add_error( 'transaction_intent_error', __( 'Can not convert to transaction, because transaction intent conversion is being processed', 'latepoint' ) );
				return false;
			}
		}

		if ( $this->is_converted() ) {
			return $this->transaction_id;
		}


		$this->mark_as_processing();

		try {

			// process payment if there is amount due
			$transaction = OsPaymentsHelper::process_payment_for_transaction_intent( $this );
			if ( ! $transaction || $transaction->status != LATEPOINT_TRANSACTION_STATUS_SUCCEEDED ) {
				if ( ! $transaction ) {
					$this->add_error( 'transaction_intent_error', __( 'No payment processor available to process this transaction intent', 'latepoint' ) );
				} else {
					if ( $transaction->get_error() ) {
						$this->add_error( 'transaction_intent_error', $transaction->get_error_messages() );
					}
				}
				$this->mark_as_new();
				return false;
			}

			/**
			 * Filters transaction right before it's about to be saved when converting from a transaction intent
			 *
			 * @param {OsTransactionModel} $transaction Transaction to be filtered
			 * @returns {OsTransactionModel} The filtered transaction
			 *
			 * @since 5.0.0
			 * @hook latepoint_before_transaction_save_from_transaction_intent
			 *
			 */
			$transaction = apply_filters( 'latepoint_before_transaction_save_from_transaction_intent', $transaction );


			if ( $transaction->save() ) {
				$this->mark_as_converted( $transaction );

				/**
				 * Transaction was created
				 *
				 * @param {OsTransactionModel} $transaction instance of transaction model that was created
				 *
				 * @since 5.0.0
				 * @hook latepoint_transaction_created
				 *
				 */
				do_action( 'latepoint_transaction_created', $transaction );

				if ( $transaction->invoice_id ) {
					$invoice = new OsInvoiceModel( $transaction->invoice_id );
					if ( $invoice && ! $invoice->is_new_record() ) {
						$invoice->change_status( LATEPOINT_INVOICE_STATUS_PAID );
						OsOrdersHelper::check_if_order_invoices_paid_full_balance( $this->order_id );
					}
				}

				return $transaction->id;
			} else {
				$this->add_error( 'transaction_intent_error', $transaction->get_error_messages() );

				$this->mark_as_new();
				return false;
			}
		} catch ( Exception $e ) {
			$this->mark_as_new();
			// translators: %s is the error description
			$this->add_error( 'transaction_intent_error', sprintf( __( 'Error: %s', 'latepoint' ), $e->getMessage() ) );
			OsDebugHelper::log( 'Error converting transaction intent to a transaction', 'transaction_intent_error', $e->getMessage() );
			return false;
		}
	}

	public function get_by_intent_key( $intent_key ) {
		return $this->where( [ 'intent_key' => $intent_key ] )->set_limit( 1 )->get_results_as_models();
	}

	public function mark_as_converted( OsTransactionModel $transaction ): bool {
		if ( empty( $transaction->id ) ) {
			return false;
		}

		$this->transaction_id = $transaction->id;
		$this->status         = LATEPOINT_TRANSACTION_INTENT_STATUS_CONVERTED;

		if ( $this->save() ) {
			/**
			 * Transaction intent is converted to transaction
			 *
			 * @param {OsTransactionIntentModel} $transaction_intent Instance of transaction intent model that has been converted to transaction
			 * @param {OsTransactionModel} $transaction Instance of transaction model that transaction intent was converted to
			 *
			 * @since 5.0.0
			 * @hook latepoint_transaction_intent_converted
			 *
			 */
			do_action( 'latepoint_transaction_intent_converted', $this, $transaction );
			return true;
		} else {
			return false;
		}
	}

	public function mark_as_failed() {
		$this->update_attributes( [ 'status' => LATEPOINT_TRANSACTION_INTENT_STATUS_FAILED ] );
		/**
		 * Transaction intent is marked as failed
		 *
		 * @param {OsTransactionIntentModel} $transaction_intent Instance of order intent model that has failed
		 *
		 * @since 5.2.0
		 * @hook latepoint_transaction_intent_failed
		 *
		 */
		do_action( 'latepoint_transaction_intent_failed', $this );
	}

	public function mark_as_processing() {
		$this->update_attributes( [ 'status' => LATEPOINT_TRANSACTION_INTENT_STATUS_PROCESSING ] );
		/**
		 * Transaction intent is marked as processing
		 *
		 * @param {OsTransactionIntentModel} $transaction_intent Instance of order intent model that has started processing
		 *
		 * @since 5.0.0
		 * @hook latepoint_transaction_intent_processing
		 *
		 */
		do_action( 'latepoint_transaction_intent_processing', $this );
	}

	public function mark_as_new() {
		$this->update_attributes( [ 'status' => LATEPOINT_TRANSACTION_INTENT_STATUS_NEW ] );
		/**
		 * Order intent is marked as new
		 *
		 * @param {OsTransactionIntentModel} $transaction_intent Instance of order intent model that is being marked as new
		 *
		 * @since 5.0.0
		 * @hook latepoint_transaction_intent_new
		 *
		 */
		do_action( 'latepoint_transaction_intent_new', $this );
	}

	// Determines if order intent has been converted into a order already
	public function is_converted(): bool {
		if ( empty( $this->transaction_id ) ) {
			return false;
		} else {
			return true;
		}
	}

	public function generate_data_vars(): array {
		$vars = [
			'id'                  => $this->id,
			'intent_key'          => $this->intent_key,
			'payment_data'        => ! empty( $this->payment_data ) ? json_decode( $this->payment_data, true ) : [],
			'order_id'            => $this->order_id,
			'transaction_id'      => $this->transaction_id,
			'order_form_page_url' => $this->order_form_page_url,
			'updated_at'          => $this->updated_at,
			'created_at'          => $this->created_at,
		];

		return $vars;
	}

	public function get_page_url_with_intent() {
		$order_form_page_url   = $this->order_form_page_url;
		$existing_var_position = strpos( $order_form_page_url, 'latepoint_transaction_intent_key=' );
		if ( $existing_var_position === false ) {
			// no intent variable in url
			$question_position = strpos( $order_form_page_url, '?' );
			if ( $question_position === false ) {
				// no ?query params
				$hash_position = strpos( $order_form_page_url, '#' );
				if ( $hash_position === false ) {
					// no hashtag in url
					$order_form_page_url = $order_form_page_url . '?latepoint_transaction_intent_key=' . $this->intent_key;
				} else {
					// hashtag in url and no ?query, prepend the hashtag with query
					$order_form_page_url = substr_replace( $order_form_page_url, '?latepoint_transaction_intent_key=' . $this->intent_key . '#', $hash_position, 1 );
				}
			} else {
				// ?query string exists, add intent key to it
				$order_form_page_url = substr_replace( $order_form_page_url, '?latepoint_transaction_intent_key=' . $this->intent_key . '&', $question_position, 1 );
			}
		} else {
			// intent key variable exist in url
			preg_match( '/latepoint_transaction_intent_key=([\d,\w]*)/', $order_form_page_url, $matches );
			if ( isset( $matches[1] ) ) {
				$order_form_page_url = str_replace( 'latepoint_transaction_intent_key=' . $matches[1], 'latepoint_transaction_intent_key=' . $this->intent_key, $order_form_page_url );
			}
		}

		return $order_form_page_url;
	}

	public function generate_intent_key() {
		$this->intent_key = bin2hex( openssl_random_pseudo_bytes( 10 ) );
	}


	public function get_customer(): OsCustomerModel {
		if ( $this->customer_id ) {
			if ( ! isset( $this->customer ) || ( isset( $this->customer ) && ( $this->customer->id != $this->customer_id ) ) ) {
				$this->customer = new OsCustomerModel( $this->customer_id );
			}
		} else {
			$this->customer = new OsCustomerModel();
		}

		return $this->customer;
	}


	protected function before_create() {
		if ( empty( $this->intent_key ) ) {
			$this->intent_key = bin2hex( openssl_random_pseudo_bytes( 10 ) );
		}
		if ( empty( $this->status ) ) {
			$this->status = LATEPOINT_TRANSACTION_INTENT_STATUS_NEW;
		}
	}

	protected function allowed_params( $role = 'admin' ) {
		$allowed_params = array(
			'payment_data',
			'intent_key',
			'order_id',
			'customer_id',
			'invoice_id',
			'transaction_id',
			'order_form_page_url',
			'status',
		);

		return $allowed_params;
	}


	protected function params_to_save( $role = 'admin' ) {
		$params_to_save = array(
			'payment_data',
			'intent_key',
			'charge_amount',
			'specs_charge_amount',
			'order_id',
			'customer_id',
			'invoice_id',
			'transaction_id',
			'status',
		);

		return $params_to_save;
	}


	protected function properties_to_validate() {
		$validations = array(
			'order_id' => array( 'presence' ),
		);

		return $validations;
	}
}
