<?php
namespace Bright_Deposits_For_WooCommerce\Pages;

use Bright_Deposits_For_WooCommerce\Core\OrderDeposit;

use Bright_Deposits_For_WooCommerce\Core\ContainerManager;

/**
 * @package Bright_Deposits_For_WooCommerce\Pages
 * @since 1.0.0
 * @author Bright Plugins
 * Description: This class is responsible for handling the checkout page functionalities for deposits in WooCommerce.
 */
class Checkout {

	/**
	 * Singleton instance of the class.
	 */
	private static $instance = null;
	
	protected $helper;

	public function __construct() {

		$this->helper = ContainerManager::get('helper');
		
		add_action( 'woocommerce_review_order_after_order_total', [$this, 'toPayHtml'] );
		add_action( 'woocommerce_payment_complete', [$this, 'manageDepositOrders'], 10, 1 );
		add_filter( 'woocommerce_payment_complete_order_status', [$this, 'changeStatusToDeposit'], 10, 3 );
		add_filter( 'woocommerce_cod_process_payment_order_status', [$this, 'offlineDepositOrders'], 10, 2 );
		add_filter( 'woocommerce_bacs_process_payment_order_status', [$this, 'offlineDepositOrders'], 10, 2 );
		add_filter( 'woocommerce_cheque_process_payment_order_status', [$this, 'offlineDepositOrders'], 10, 2 );
		add_action( 'woocommerce_checkout_cart_item_quantity', [$this, 'displayItemDepositData'], 10, 3 );
		add_filter( 'woocommerce_checkout_create_order_line_item', [$this, 'saveCartOrderMeta'], 20, 4 );
		add_action( 'woocommerce_checkout_update_order_meta', [$this, 'manageOrder'], 10, 2 );
		add_action( 'woocommerce_order_status_completed', [$this, 'changeParentOrderStatus'], 10, 1 );
		add_action( 'woocommerce_order_status_changed', [$this, 'sendNotificationPartiallypaid'], 10, 4 );

		/**
		 * Custom action hook for extending checkout behavior in this plugin.
		 *
		 * @since 1.0.0
		 * @param self $checkout Checkout page handler instance.
		 */
		do_action( 'bdfwbp_wc_deposit_checkout', $this );

		// Back-compat: keep the original (unprefixed) hook as deprecated.
		$deprecated_hook = 'wc_deposit_checkout';
		do_action_deprecated( $deprecated_hook, array( $this ), BDFWBP_VERSION, 'bdfwbp_wc_deposit_checkout' );
	}

	/**
	 * Get the singleton instance of the class.
	 * @since 1.0.0
	 * @return Checkout
	 */
	public static function instance() {
		if ( is_null( self::$instance ) ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * Prevents cloning of the instance.
	 * @since 1.0.0
	 * @return void
	 */
	private function __clone() {
		// Cloning is forbidden.
	}

	/**
	 * Prevents unserializing of the instance.
	 * @since 1.0.0
	 * @return void
	 */
	public function __wakeup() {
		// Unserializing instances of this class is forbidden.
	}
	
	/**
	 * Manage deposit orders after payment completed
	 * @since 1.0.0
	 * @param  int $order_id
	 * @return void
	 */
	public function manageDepositOrders( $order_id ): void {

		$order = wc_get_order( $order_id );
		
		/**
		 * Check if the order is a deposit order and if child deposit orders have not been generated yet.
		 * If so, generate child deposit orders based on the parent order.
		 */
		if ( $this->helper->isDepositOrder( $order_id ) && ! $order->get_meta( '_deposit_orders_generated', true ) ) {
			/**
			 * BDFWBP_CUSTOM_ACTION_HOOK: Allows developers to add functions before generate deposit orders.
			 * @since 1.0.0
			 * @param \WC_Order $order (parent order)
			 * @return void
			 */
			do_action( 'bdfwbp_before_generate_deposit_orders', $order );

			$this->generateDepositOrder( $order );
		}

		/**
		 * BDFWBP_CUSTOM_ACTION_HOOK: Allows developers to add functions after generate deposit orders.
		 * @since 1.0.0
		 * @param \WC_Order $order (parent order)
		 * @return void
		 */
		do_action( 'bdfwbp_after_generate_deposit_orders', $order );

	}

	/**
	 * Change order status to 'deposit' after first payment complete
	 *
	 * @param  string $status
	 * @param  int    $order_id
	 * @param  object $order
	 * @return string
	 * @since 1.0.0
	 */
	public function changeStatusToDeposit( $status, $order_id, $order ) {
		if ( $this->helper->isDepositOrder( $order_id ) ) {

			$due_ammount = $order->get_total() - $order->get_meta( '_deposit_value', true );
			$order->update_meta_data( '_order_due_ammount', $due_ammount );
			$order->save();

			$order->calculate_shipping();
			$order->calculate_totals();

			/**
			 * BDFWBP_CUSTOM_FILTER_HOOK: Allows developers to modify the order status after first deposit payment complete when online payment is used.
			 * @since 1.0.0
			 * @param string    $status The order status. 'wc-partially-paid' is the default status.
			 * @param \WC_Order $order (parent order)
			 * @return string
			 */
			return apply_filters( 'bdfwbp_order_status_change_online_payments', 'wc-partially-paid', $order );
		}

		return $status;
	}

	/**
	 * Update parent order meta data when all payments made
	 *
	 * @param  int $order_id
	 * @return void
	 * @since 1.0.0
	 */
	public function updateParentMetaData( $order_id ): void {
		$order = wc_get_order( $order_id );

		if(!$order instanceof \WC_Order){
			return;
		}

		$order->update_meta_data( '_order_due_ammount', 0 );
		$order->save();

		/**
		 * BDFWBP_CUSTOM_ACTION_HOOK: Allows developers to add functions after update parent meta data.
		 * @since 1.0.0
		 * @param int $order_id
		 * @param \WC_Order $order
		 * @return void
		 */
		do_action( 'bdfwbp_after_update_parent_meta_data', $order );
	}

	/**
	 * Handle offline deposit orders
	 *
	 * @param  string $status
	 * @param  object $order
	 * @return string
	 * @since 1.0.0
	 */
	public function offlineDepositOrders( $status, $order ) {
		$order_id = $order->get_id();
		//update the shipping cost if exclude shipping cost option enable
		if ( $this->helper->isDepositOrder( $order_id ) ) {

			$due_ammount = $order->get_total() - $order->get_meta( '_deposit_value', true );
			$order->update_meta_data( '_order_due_ammount', $due_ammount );
			$order->save();

			$order->calculate_shipping();
			$order->calculate_totals();

		}

		if ( $this->helper->isDepositOrder( $order_id ) && ! $order->get_meta( '_deposit_orders_generated', true ) ) {

			if ( $order->get_total() > 0 ) {
				
				// create deposit orders based on parent order
				$this->generateDepositOrder( $order );
				/**
				 * BDFWBP_CUSTOM_FILTER_HOOK: Allows developers to modify the order status after first deposit payment complete when offline payment is used.
				 * @since 1.0.0
				 * @param string    $status The order status. 'wc-partially-paid' is the default status.
				 * @param \WC_Order $order (parent order) 
				 * @return string
				 */
				return apply_filters( 'bdfwbp_order_status_change_offline_payments', 'wc-partially-paid', $order );
			}

		}
		return $status;
	}
	
	/**
	 * Change parent order status when all deposit payments are completed
	 *
	 * @param  int $order_id
	 * @return void
	 * @since 1.0.0
	 */
	public function changeParentOrderStatus( $child_order_id ): void {

		$deposit_order = wc_get_order( $child_order_id );

		if(! $deposit_order instanceof \WC_Order){
			return;
		}

		if ( 'shop_deposit' !== $deposit_order->get_type() ) {
			return;
		}

		$parent_id = $deposit_order->get_parent_id();

		if ( ! $parent_id ) {
			return;
		}

		$parent_order = wc_get_order( $parent_id );

		if ( ! $parent_order instanceof \WC_Order ) {
			return;
		}

		// Count total number of deposit installments for the parent order.
		$total_installments = wc_get_orders( [
			'type'   => 'shop_deposit',
			'parent' => $parent_id,
			'limit'  => -1,
			'return' => 'ids',
		] );

		// Count completed deposit installments for the parent order.
		$completed_installments = wc_get_orders( [
			'type'   => 'shop_deposit',
			'parent' => $parent_id,
			'status' => 'completed',
			'limit'  => -1,
			'return' => 'ids',
		] );

		if ( count( $total_installments ) === count( $completed_installments ) ) {
			$new_status = $this->helper->getOptionValue( 'fully_paid_status' );
			$parent_order->update_status( $new_status );
		}
	}

	/**
	 * Manage order meta data for deposits
	 *
	 * @param  int $order_id
	 * @param  array $posted_data
	 * @return void
	 * @since 1.0.0
	 */
	public function manageOrder( $order_id, $posted_data ): void {

		$order = wc_get_order( $order_id );

		if(!$order instanceof \WC_Order){
			return;
		}

		$cart  = WC()->cart;

		if ( ! $cart || $cart->is_empty() ) {
			return;
		}

		$initial_payment_total = 0;
		$future_payment_total = 0;

		// 1. Calculate base deposit and due values from item lines
		foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
			if ( isset( $cart_item['_deposit_mode'] ) && $cart_item['_deposit_mode'] === 'check_deposit' ) {
				$initial_payment_total += $cart_item['_deposit'] * $cart_item['quantity'];
				$future_payment_total += $cart_item['_due_payment'] * $cart_item['quantity'];
			} else {
				// This is a full payment item, add its line total to the initial payment
				$initial_payment_total += $cart_item['line_total'];
			}
		}

		// 2. Add Taxes, Shipping, and Fees to the initial payment total
		if ( wc_tax_enabled() && !wc_prices_include_tax() ) {
			$total_tax = $cart->get_total_tax();
			$initial_payment_total += $total_tax;
		}

		if ( $cart->needs_shipping() ) {
			$shipping_total = $cart->get_shipping_total();
			$initial_payment_total += $shipping_total;
		}

		$fee_total = $cart->get_fee_total();
		if ( $fee_total > 0 ) {
			$initial_payment_total += $fee_total;
		}

		// 3. Save meta data if there is a due payment
		if ( $future_payment_total > 0 ) {
			$original_total = $initial_payment_total + $future_payment_total;
			$order->update_meta_data( '_deposit_value', $initial_payment_total );
			$order->update_meta_data( '_due_payment_value', $future_payment_total );
			$order->update_meta_data( '_original_order_total', $original_total );
			$order->save();
		}
	}

	/**
	 * Generate deposit orders based on the parent order
	 *
	 * @param  object $order
	 * @return void
	 * @since 1.0.0
	 */
	private function generateDepositOrder( $order ): void {
		$order_id = $order->get_id();
		$initial_payment_total = $order->get_meta( '_deposit_value', true );
		$future_payment_total = $order->get_meta( '_due_payment_value', true );
		$offline_payment_gatway_ids = ['bacs', 'cheque', 'cod'];
		$first_deposit_order_status = 'completed';
		
		if ( in_array( $order->get_payment_method(), $offline_payment_gatway_ids ) ) {
			$first_deposit_order_status = 'on-hold';
		}

		/**
		 * Collect original product data from the parent order
		 */
		$original_products_data = [];
		foreach ( $order->get_items() as $item_id => $item ) {
			$product = $item->get_product();
			if ( $product ) {
				$original_products_data[] = [
					'name'     => $item->get_name(),
					'quantity' => $item->get_quantity(),
					'id'       => $product->get_id(),
					'price'    => $product->get_price()
				];
			}
		}
		$serialized_products = json_encode( $original_products_data );
		// Get parent order addresses to prevent fatal errors on email generation for child orders.
		$billing_address  = $order->get_address( 'billing' );
		$shipping_address = $order->get_address( 'shipping' );

		/**
		 * Create new child deposit order
		 * This order will be used to pay the due payment amount.
		 */
		$DueOrder = new OrderDeposit();

		$total = $order->get_total();

		// Order details
		$DueOrder->set_customer_id( $order->get_user_id() );
		$DueOrder->set_parent_id( $order->get_id() );
		$DueOrder->set_address( $billing_address, 'billing' );
		$DueOrder->set_address( $shipping_address, 'shipping' );

		// Set address props to prevent fatal errors on email generation
		foreach ( $billing_address as $key => $value ) {
			$DueOrder->{"set_billing_{$key}"}( $value );
		}
		foreach ( $shipping_address as $key => $value ) {
			$DueOrder->{"set_shipping_{$key}"}( $value );
		}

		// Fee items
		$item = new \WC_Order_Item_Fee();
		$item->set_name( $this->helper->getOptionValue( 'bdfw_due_payment_for_order_label' ) . ' #' . $order->get_id() . '-2' );
		$item->set_total( $future_payment_total );
		$item->set_total_tax( 0 );
		$item->save();
		$DueOrder->add_item( $item );
		$DueOrder->calculate_totals( $this->helper->applyTaxforDepositOrder() );
		$DueOrder->update_meta_data( '_deposit_id', $order->get_id() . '-2', true );
		
        $next_payment_date = $this->helper->getNextPaymentDate();
		$next_payment_date_timestamp = strtotime( $next_payment_date );
		$calculated_due_date = gmdate( 'Y-m-d H:i:s', $next_payment_date_timestamp );

		$DueOrder->update_meta_data( '_deposit_due_date', $calculated_due_date, true );
		$DueOrder->update_meta_data( '_original_parent_order_products', $serialized_products, true ); // Store product data

		/**
		 * BDFWBP_CUSTOM_FILTER_HOOK: Allows developers to modify the due order status before saving order meta data.
		 * @since 1.0.0
		 * @param string $first_deposit_order_status The due order status. 'pending' is the default status.
		 * @param \Bright_Deposits_For_WooCommerce\Core\OrderDeposit $DueOrder The due order object.
		 * @return string
		 */
		$DueOrder->set_status( apply_filters( 'bdfwbp_due_order_status', 'pending', $DueOrder ) );

		$DueOrder->save();

		
		/**
		 * Create new child deposit order
		 * This order will be used to pay the initial deposit amount.
		 */
		$DepositOrder = new OrderDeposit();
		
		// Order details
		$DepositOrder->set_customer_id( $order->get_user_id() );
		$DepositOrder->set_payment_method( $order->get_payment_method() );
		$DepositOrder->set_parent_id( $order->get_id() );
		$DepositOrder->set_address( $billing_address, 'billing' );
		$DepositOrder->set_address( $shipping_address, 'shipping' );

		// Set address props to prevent fatal errors on email generation
		foreach ( $billing_address as $key => $value ) {
			$DepositOrder->{"set_billing_{$key}"}( $value );
		}
		foreach ( $shipping_address as $key => $value ) {
			$DepositOrder->{"set_shipping_{$key}"}( $value );
		}

		// Fee items
		$item = new \WC_Order_Item_Fee();
		$item->set_name( $this->helper->getOptionValue( 'bdfw_deposit_payment_for_order_label' ) . ' #' . $order->get_id() . '-1' );
		$item->set_total( $initial_payment_total );
		$item->set_total_tax( 0 );
		$item->save();

		$DepositOrder->add_item( $item );
		$DepositOrder->calculate_totals( false );
		$DepositOrder->update_meta_data( '_deposit_id', $order->get_id() . '-1', true );
		$DepositOrder->update_meta_data( '_original_parent_order_products', $serialized_products, true ); // Store product data

		/**
		 * BDFWBP_CUSTOM_FILTER_HOOK: Allows developers to modify the deposit order status before saving order meta data. 
		 * If offline payment gateway is used, the status will be 'on-hold'. Otherwise, the status will be 'completed'.
		 * @since 1.0.0
		 * @param string $first_deposit_order_status The deposit order status. 'completed' is the default status.
		 * @param \Bright_Deposits_For_WooCommerce\Core\OrderDeposit $DepositOrder The deposit order object.
		 * @return string
		 */
		$DepositOrder->set_status( apply_filters( 'bdfwbp_deposit_order_status', $first_deposit_order_status, $DepositOrder ) );

		$DepositOrder->save();

		$due_amount = $order->get_total() - $order->get_meta( '_deposit_value', true );
		$order->update_meta_data( '_order_due_ammount', $due_amount );

		// add order meta data because deposit order generate successfully
		$order->update_meta_data( '_deposit_orders_generated', true );

		/**
		 * BDFWBP_CUSTOM_ACTION_HOOK: Allows developers to add functions after generate deposit orders.
		 * @since 1.0.0
		 * @param int $order_id
		 * @return void
		 */
		do_action( 'bdfwbp_deposit_orders_generated', $order->get_id() );
	}

	/**
	 * Save cart order meta data
	 *
	 * @param  object $item
	 * @param  string $cart_item_key
	 * @param  array $values
	 * @param  object $order
	 * @return void
	 * @since 1.0.0
	 */
	public function saveCartOrderMeta( $item, $cart_item_key, $values, $order ): void {
		
		/**
		 * BDFWBP_CUSTOM_ACTION_HOOK: Allows developers to add code before saving cart order meta data.
		 * @since 1.0.0
		 * @param object $item The item object.
		 * @param string $cart_item_key The cart item key.
		 * @param array $values The values array.
		 * @param object $order The order object.
		 * @return void
		 */
		do_action( 'bdfwbp_before_save_cart_order_meta', $item, $cart_item_key, $values, $order );
		
		foreach ( $item as $cart_item_key => $values ) {
			if ( isset( $values['_deposit'] ) && $values['_deposit_mode'] == 'check_deposit' ) {
				$deposit_amount = $values['_deposit'] * $item->get_quantity();
				$item->add_meta_data( '_deposit', apply_filters( 'bdfwbp_order_item_deposit_amount', $deposit_amount, $item, $values, $order ), true );
			}

			if ( isset( $values['_due_payment'] ) && $values['_deposit_mode'] == 'check_deposit' ) {
				$due_payment = $values['_due_payment'] * $item->get_quantity();
				$item->add_meta_data( '_due_payment', apply_filters( 'bdfwbp_order_item_due_payment', $due_payment, $item, $values, $order ), true );
			}
		}

		/**
		 * BDFWBP_CUSTOM_ACTION_HOOK: Allows developers to add code after saving cart order meta data.
		 * @since 1.0.0
		 * @param object $item The item object.
		 * @param string $cart_item_key The cart item key.
		 * @param array $values The values array.
		 * @param object $order The order object.
		 * @return void
		 */
		do_action( 'bdfwbp_after_save_cart_order_meta', $item, $cart_item_key, $values, $order );
	}

	/**
	 * Display deposit data below the cart item in order review section
	 *
	 * @param  string $quantity_html
	 * @param  array  $cart_item
	 * @param  string $cart_item_key
	 * @return string
	 * @since 1.0.0
	 */
	public function displayItemDepositData( $quantity_html, $cart_item, $cart_item_key ) {
		if ( isset( $cart_item['_deposit_mode'] ) && $cart_item['_deposit_mode'] === 'check_deposit' && isset($cart_item['_deposit']) ) {
			
			$line_deposit_total = $cart_item['_deposit'] * $cart_item['quantity'];
			$line_due_total = $cart_item['_due_payment'] * $cart_item['quantity'];

			$deposit_info_html = sprintf(
				'<div class="bdfw-checkout-item-deposit-info" style="font-size: 0.9em; color: #666;">' . 
				$this->helper->getOptionValue( 'bdfw_deposit_label' ) . 
				': %s <br> ' . 
				$this->helper->getOptionValue( 'bdfw_due_payment_label' ) . 
				': %s</div>',
				wc_price( $line_deposit_total ),
				wc_price( $line_due_total )
			);

			/**
			 * BDFWBP_CUSTOM_FILTER_HOOK: Allows developers to modify the deposit info HTML.
			 * @since 1.0.0
			 * @param string $deposit_info_html The deposit info HTML.
			 * @param array $cart_item The cart item data.
			 * @param string $cart_item_key The cart item key.
			 * @return string
			 */
			$quantity_html .= apply_filters( 'bdfwbp_checkout_item_deposit_info_html', $deposit_info_html, $cart_item, $cart_item_key );
		}

		return $quantity_html;
	}

	/**
	 * Displays the deposit and due payment totals on the checkout page.
	 *
	 * This is hooked into 'woocommerce_review_order_after_order_total'.
	 * It only displays the information if the cart contains a deposit item
	 * and the total amount to be paid is greater than zero.
	 *
	 * @since 1.0.0
	 * @return void
	 */
	public function toPayHtml(): void {
		$this->helper->maybeDisplayToPayHtml();
	}

	/**
	 * Send notification for partially paid orders.
	 *
	 * @param int $order_id
	 * @param string $old_status
	 * @param string $new_status
	 * @param WC_Order $order
	 * @return void
	 */
	public function sendNotificationPartiallyPaid( $order_id, $old_status, $new_status, $order ) {
		if ( $new_status === 'partially-paid' ) {
			// Get the mailer instance
			$mailer = WC()->mailer()->get_emails();

			// Trigger the email for the customer
			if ( isset( $mailer['WC_Customer_New_Deposit_Order'] ) ) {
				$email = $mailer['WC_Customer_New_Deposit_Order'];
				if ( is_object( $email ) && method_exists( $email, 'trigger' ) ) {
					call_user_func( array( $email, 'trigger' ), $order_id, $order );
				}
			}

			// Trigger the email for the admin
			if ( isset( $mailer['WC_Admin_New_Deposit_Order'] ) ) {
				$email = $mailer['WC_Admin_New_Deposit_Order'];
				if ( is_object( $email ) && method_exists( $email, 'trigger' ) ) {
					call_user_func( array( $email, 'trigger' ), $order_id, $order );
				}
			}
		}
	}
}
