<?php

namespace CommerceBird\Admin\Actions\Sync;

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

use CommerceBird\Admin\Actions\Ajax\ExactOnlineAjax;
use CommerceBird\Admin\Connectors\Connector;

/**
 * Handles synchronization of products, customers, and orders between WooCommerce and Exact Online.
 */
class ExactOnlineSync {

	/**
	 * Whether to enable stock synchronization.
	 *
	 * @var bool Whether to enable stock synchronization.
	 * @since 2.6.5
	 */
	public static bool $enable_eo_stock;

	/**
	 * Construct to load options from wp_options
	 */
	public function __construct() {
		// Load any required options or settings here if needed.
		self::$enable_eo_stock = get_option( 'cmbird_enable_eo_stock', false );
	}

	/**
	 * Sync data from Exact Online.
	 *
	 * @param string $type product|customer.
	 * @param array  $data to sync from Exact Online.
	 * @param bool   $import import or update.
	 * @return mixed
	 */
	public function sync( string $type, array $data, bool $import = false ) {
		if ( empty( $type ) ) {
			return false;
		}
		if ( $import ) {
			$this->import( $type, $data );
		} else {
			$this->update( $type, $data );
		}
	}
	/**
	 * Import data from Exact Online.
	 *
	 * For product data will be like,
	 * {
	 * "Code":string,
	 * "Description": string,
	 * "ID": string,
	 * "IsSalesItem": bool,
	 * "PictureName": null|string,
	 * "PictureUrl": string,
	 * "StandardSalesPrice": float,
	 * "Stock": int
	 * },
	 *
	 * for Order data will be like,
	 * {
	 * "MainContact": null|string,
	 * "Email": null|string,
	 * "ID": string,
	 * "Name": string,
	 * "AddressLine1": null|string,
	 * "AddressLine2": null|string,
	 * "City": string,
	 * "Country": string,
	 * "Phone": null|string,
	 * "Postcode": string
	 * }
	 *
	 * @param string $type of provided data.
	 * @param array  $data of import.
	 * @return mixed
	 */
	public static function import( string $type, array $data ) {
		// add logging.
		// $fd = fopen( __DIR__ . '/import.txt', 'a+' );
		$endpoint = '';
		$payload  = array();

		switch ( $type ) {
			case 'product':
				$endpoint      = '/wc/v3/products/batch';
				$filtered_data = array_filter(
					$data,
					function ( $item ) {
						// Exclude item if product already exists via SKU.
						return ! wc_get_product_id_by_sku( $item['Code'] );
					}
				);
				$payload       = array(
					'create' => array_map(
						function ( $item ) {
								// Skip image import if PictureName is 'placeholder_item'.
								$images = array();
							if ( isset( $item['PictureName'] ) && 'placeholder_item' !== $item['PictureName'] ) {
									$image_id = self::get_existing_image_id( $item['PictureName'] );
									// If image exists, add it to the images array, else upload the image and add it to the images array.
								if ( $image_id ) {
									$images[] = array( 'id' => $image_id );
								} else {
									$image_id = self::upload_image( $item['PictureUrl'], $item['PictureName'] );
									// If image upload is successful, add it to the images array.
									if ( $image_id ) {
										$images[] = array( 'id' => $image_id );
									}
								}
							}
								// Check if category exists with $item['ItemGroupDescription'], else create it and get the ID.
							if ( isset( $item['ItemGroupDescription'] ) ) {
								$term = get_term_by( 'name', $item['ItemGroupDescription'], 'product_cat' );
								$term_id = $term->term_id;
								if ( empty( $term_id ) ) {
									$term = wp_insert_term(
										$item['ItemGroupDescription'],
										'product_cat',
										array(
											'parent' => 0,
										)
									);
									$term_id = $term['term_id'];
								}
							} else {
								$term_id = 0;
							}
								// if stock exists then set manage_stock to true && if stock is enabled in settings.
								$manage_stock = ( isset( $item['Stock'] ) && self::$enable_eo_stock ) ? true : false;
								$item_stock = isset( $item['Stock'] ) ? $item['Stock'] : null;
								return array(
									'name'               => $item['Description'],
									'sku'                => $item['Code'],
									'status'             => 'publish',
									'type'               => 'simple',
									'regular_price'      => (string) $item['StandardSalesPrice'],
									'images'             => $images,
									'stock_quantity'     => $item_stock,
									'manage_stock'       => $manage_stock,
									'cost_of_goods_sold' => ( isset( $item['CostPriceStandard'] ) ) ? (float) $item['CostPriceStandard'] : null,
									'categories'         => array(
										array(
											'id' => $term_id,
										),
									),
									'meta_data'          => array(
										array(
											'key'   => 'eo_item_id',
											'value' => $item['ID'],
										),
									),
								);
						},
						$filtered_data
					),
				);
				break;

			case 'customer':
				$endpoint      = '/wc/v3/customers/batch';
				$filtered_data = array_filter(
					$data,
					function ( $item ) {
						// Exclude item if customer already exists via email.
						return ! get_user_by( 'email', $item['Email'] );
					}
				);
				$payload       = array(
					'create' => array_map(
						function ( $item ) {
							if ( empty( $item['Email'] ) ) {
									return null;
							}
							if ( ! empty( $item['VATNumber'] ) ) {
								$first_name = '';
								$last_name = '';
								$company = $item['Name'];
							} else {
								$names = explode( ' ', $item['Name'] );
								$first_name = array_shift( $names ); // Take the first word as first name.
								$last_name = implode( ' ', $names ); // Join the rest as last name.
							}
							// generate username based on email.
							$username = explode( '@', $item['Email'] )[0];
							// add random number to username if it already exists.
							$existing_user = get_user_by( 'login', $username );
							if ( $existing_user ) {
								$username .= '_' . wp_rand( 1000, 9999 );
							}
							$address = array(
								'first_name' => $first_name,
								'last_name'  => $last_name,
								'company'    => $company ?? '',
								'address_1'  => $item['AddressLine1'] ?? '',
								'address_2'  => $item['AddressLine2'] ?? '',
								'city'       => $item['City'] ?? '',
								'country'    => $item['Country'] ?? '',
								'postcode'   => $item['Postcode'] ?? '',
								'phone'      => $item['Phone'] ?? '',
								'email'      => $item['Email'],
							);
							return array(
								'email'      => $item['Email'],
								'first_name' => $first_name,
								'last_name'  => $last_name,
								'username'   => $username,
								'billing'    => $address,
								'shipping'   => $address,
								'meta_data'  => array(
									array(
										'key'   => 'eo_account_id',
										'value' => $item['ID'],
									),
									array(
										'key'   => 'eo_contact_id',
										'value' => $item['MainContact'] ?? '',
									),
								),
							);
						},
						array_filter( $filtered_data, fn( $item ) => ! empty( $item['Email'] ) )
					),
				);
				break;

			default:
				return false;
		}

		if ( empty( $payload['create'] ) ) {
			return false;
		}
		// log the payload.
		// fwrite( $fd, print_r( $payload, true ) );
		add_filter( 'woocommerce_rest_check_permissions', '__return_true' );
		$request = new \WP_REST_Request( 'POST', $endpoint );
		$request->set_body_params( $payload );
		$response = rest_do_request( $request );
		remove_filter( 'woocommerce_rest_check_permissions', '__return_true' );
		// log the response.
		// fwrite( $fd, print_r( $response, true ) );
		// fclose( stream: $fd );
		return $response;
	}

	/**
	 * Update data based on Exact Online.
	 *
	 * @param string $type of provided data.
	 * @param array  $data to update in Exact Online.
	 * @return mixed
	 */
	public function update( string $type, array $data ) {
		// $fd = fopen( __DIR__ . '/update.txt', 'w+' );
		$endpoint          = '';
		$payload           = array();
		$is_product_update = false;
		$simple_updates    = array();
		$variation_updates = array();
		// log data.
		// fwrite( $fd, print_r( $data, true ) );

		switch ( $type ) {
			case 'product':
					$is_product_update = true;
				// Track all SKUs from Exact Online for this batch.
				$exact_skus           = array_column( $data, 'Code' );
				$existing_synced_skus = get_option( 'cmbird_eo_synced_skus', array() );
				if ( ! is_array( $existing_synced_skus ) ) {
					$existing_synced_skus = array();
				}
				$existing_synced_skus = array_merge( $existing_synced_skus, $exact_skus );
				update_option( 'cmbird_eo_synced_skus', array_values( array_unique( $existing_synced_skus ) ), false );

				$filtered_data = array_filter(
					$data,
					function ( $item ) {
						// Exclude item if product does not exists via SKU.
						return wc_get_product_id_by_sku( $item['Code'] );
					}
				);

				foreach ( $filtered_data as $item ) {
					// Check if product exists via SKU, else get the product ID by title.
					$product_id = wc_get_product_id_by_sku( $item['Code'] ) ? wc_get_product_id_by_sku( $item['Code'] ) : self::get_product_id_by_title( $item['Description'] );
					if ( empty( $product_id ) ) {
						continue;
					}

					// Check if post exists in the database.
					if ( ! get_post( $product_id ) ) {
						continue;
					}

					$product = wc_get_product( $product_id );
					if ( ! $product ) {
						continue;
					}

					// Mark product/variation as synced with current timestamp.
					update_post_meta( $product_id, '_eo_last_synced', time() );

					$stock_status       = '';
					$backorders_allowed = get_post_meta( $product_id, '_backorders', true ) === 'yes';
					if ( isset( $item['Stock'] ) && self::$enable_eo_stock ) {
						if ( $item['Stock'] <= 0 ) {
							$stock_status = $backorders_allowed ? 'outofstock' : 'instock';
						} else {
							$stock_status = 'instock';
						}
					}

					if ( 'product_variation' === get_post_type( $product_id ) ) {
						$parent_id = (int) wp_get_post_parent_id( $product_id );
						if ( empty( $parent_id ) ) {
							continue;
						}

						$variation_payload = array(
							'id'                 => $product_id,
							'stock_quantity'     => ( isset( $item['Stock'] ) && self::$enable_eo_stock ) ? (int) $item['Stock'] : null,
							'manage_stock'       => ( isset( $item['Stock'] ) && self::$enable_eo_stock ) ? true : false,
							'stock_status'       => $stock_status,
							'cost_of_goods_sold' => ( isset( $item['CostPriceStandard'] ) ) ? (float) $item['CostPriceStandard'] : null,
							'meta_data'          => array(
								array(
									'key'   => 'eo_item_id',
									'value' => $item['ID'],
								),
							),
						);
						if ( isset( $item['StandardSalesPrice'] ) && '' !== trim( (string) $item['StandardSalesPrice'] ) ) {
							$variation_payload['regular_price'] = (string) $item['StandardSalesPrice'];
						}

						$variation_updates[ $parent_id ][] = $variation_payload;
						continue;
					}

					// Only import an Exact image when the Woo product does not already have one.
					if ( ! $product->get_image_id() && isset( $item['PictureName'] ) && 'placeholder_item' !== $item['PictureName'] ) {
						$image_id = self::get_existing_image_id( $item['PictureName'] );
						if ( $image_id ) {
							set_post_thumbnail( $product_id, $image_id );
							update_post_meta( $image_id, '_wp_attachment_image_alt', $item['Description'] );
							update_post_meta( $product_id, '_thumbnail_id', $image_id );
							wp_update_image_subsizes( $image_id );
						} else {
							$image_id = self::upload_image( $item['PictureUrl'], $item['PictureName'] );
							if ( $image_id ) {
								set_post_thumbnail( $product_id, $image_id );
								update_post_meta( $image_id, '_wp_attachment_image_alt', $item['Description'] );
								update_post_meta( $product_id, '_thumbnail_id', $image_id );
								wp_update_image_subsizes( $image_id );
							}
						}
					}

					// Update product category for simple/parent products only.
					if ( isset( $item['ItemGroupDescription'] ) ) {
						$term    = get_term_by( 'name', $item['ItemGroupDescription'], 'product_cat' );
						$term_id = $term->term_id;
						if ( empty( $term_id ) ) {
							$term    = wp_insert_term(
								$item['ItemGroupDescription'],
								'product_cat',
								array(
									'parent' => 0,
								)
							);
							$term_id = $term['term_id'];
						}
						wp_set_object_terms( $product_id, $term_id, 'product_cat' );
					}

					$product_payload = array(
						'id'                 => $product_id,
						'stock_quantity'     => ( isset( $item['Stock'] ) && self::$enable_eo_stock && ! $product->is_type( 'variable' ) ) ? (int) $item['Stock'] : null,
						'manage_stock'       => ( isset( $item['Stock'] ) && self::$enable_eo_stock && ! $product->is_type( 'variable' ) ) ? true : false,
						'stock_status'       => $stock_status,
						'name'               => $item['Description'],
						'slug'               => sanitize_title( $item['Description'] ),
						'cost_of_goods_sold' => ( isset( $item['CostPriceStandard'] ) ) ? (float) $item['CostPriceStandard'] : null,
						'meta_data'          => array(
							array(
								'key'   => 'eo_item_id',
								'value' => $item['ID'],
							),
						),
					);

					if ( isset( $item['StandardSalesPrice'] ) && '' !== trim( (string) $item['StandardSalesPrice'] ) ) {
						$product_payload['regular_price'] = (string) $item['StandardSalesPrice'];
					}

					$simple_updates[] = $product_payload;
				}
				break;

			case 'customer':
				$endpoint = '/wc/v3/customers/batch';
				$payload  = array(
					'update' => array_map(
						function ( $item ) {
							$user = get_user_by( 'email', $item['Email'] );
							return $user ? array(
								'id'        => $user->ID,
								'meta_data' => array(
									array(
										'key'   => 'eo_account_id',
										'value' => $item['ID'],
									),
									array(
										'key'   => 'eo_contact_id',
										'value' => $item['MainContact'] ?? '',
									),
								),
								// if $item['VATNumber'] is not empty then update the billing company name.
								'billing'   => ! empty( $item['VATNumber'] ) ? array(
									'company' => $item['Name'],
								) : null,
							) : null;
						},
						array_filter( $data, fn( $item ) => get_user_by( 'email', $item['Email'] ) )
					),
				);
				break;

			case 'orders':
				$endpoint = '/wc/v3/orders/batch';
				$payload  = array(
					'update' => array_map(
						function ( $item ) {
							return array(
								'id'        => $item['Description'],
								'meta_data' => array(
									array(
										'key'   => 'eo_order_id',
										'value' => $item['OrderID'],
									),
									array(
										'key'   => 'eo_order_number',
										'value' => $item['OrderNumber'],
									),
								),
							);
						},
						$data
					),
				);
				break;

			default:
				return false;
		}

		if ( $is_product_update ) {
			if ( empty( $simple_updates ) && empty( $variation_updates ) ) {
				return false;
			}

			add_filter( 'woocommerce_rest_check_permissions', '__return_true' );
			$response = false;

			if ( ! empty( $simple_updates ) ) {
				/**
				 * Filters the batch payload for simple/parent products before it is sent to the WooCommerce REST API.
				 *
				 * Each item in the array corresponds to a single product update and contains keys such as
				 * `id`, `stock_quantity`, `manage_stock`, `stock_status`, `name`, `slug`, `regular_price`,
				 * and `meta_data`. You may remove, modify, or add keys to any item.
				 * Return an empty array to skip the batch request entirely.
				 *
				 * @param array[] $simple_updates Array of product update payloads.
				 */
				$simple_updates = apply_filters( 'cmbird_eo_product_simple_updates', $simple_updates );
				if ( ! empty( $simple_updates ) ) {
					$request = new \WP_REST_Request( 'POST', '/wc/v3/products/batch' );
					$request->set_body_params(
						array(
							'update' => $simple_updates,
						)
					);
					$response = rest_do_request( $request );
				}
			}

			foreach ( $variation_updates as $parent_id => $updates ) {
				foreach ( array_chunk( $updates, 100 ) as $updates_chunk ) {
					/**
					 * Filters the batch payload for a chunk of variation updates before it is sent to the WooCommerce REST API.
					 *
					 * Each item in the array corresponds to a single variation update and contains keys such as
					 * `id`, `stock_quantity`, `manage_stock`, `stock_status`, `regular_price`, and `meta_data`.
					 * You may remove, modify, or add keys to any item.
					 * Return an empty array to skip this chunk entirely.
					 *
					 * @param array[] $updates_chunk  Array of variation update payloads for this chunk.
					 * @param int     $parent_id      The parent product ID these variations belong to.
					 */
					$updates_chunk = apply_filters( 'cmbird_eo_product_variation_updates', $updates_chunk, (int) $parent_id );
					if ( empty( $updates_chunk ) ) {
						continue;
					}
					$variation_endpoint = sprintf( '/wc/v3/products/%d/variations/batch', (int) $parent_id );
					$request            = new \WP_REST_Request( 'POST', $variation_endpoint );
					$request->set_body_params(
						array(
							'update' => $updates_chunk,
						)
					);
					$response = rest_do_request( $request );
				}
			}

			remove_filter( 'woocommerce_rest_check_permissions', '__return_true' );
			return $response;
		}
		// fwrite( $fd, print_r( $payload, true ) );
		if ( empty( $payload['update'] ) ) {
			return false;
		}
		add_filter( 'woocommerce_rest_check_permissions', '__return_true' );
		$request = new \WP_REST_Request( 'POST', $endpoint );
		$request->set_body_params( $payload );
		$response = rest_do_request( $request );
		remove_filter( 'woocommerce_rest_check_permissions', '__return_true' );
		// fwrite( $fd, 'response: ' . print_r( $response, true ) );
		// fclose( $fd );
		return $response;
	}

	private static function get_product_id_by_title( string $product_title ) {
		// Set up the query arguments.
		$args = array(
			'post_type'      => 'product',
			'posts_per_page' => 1,
			'fields'         => 'ids',
			's'              => $product_title, // Search by product title.
		);

		// Run the query.
		$query = new \WP_Query( $args );

		// Get the product ID from the query results.
		$product_id = $query->post_count > 0 ? $query->posts[0] : 0;

		// Reset post data.
		wp_reset_postdata();

		return $product_id;
	}

	/**
	 * Whether Exact Online cron work should be skipped for configured quiet hours.
	 * Uses the site's configured timezone from WordPress settings.
	 *
	 * @return bool
	 */
	private static function is_exact_cron_quiet_hours(): bool {
		$current_hour = (int) wp_date( 'G', time(), wp_timezone() );

		return $current_hour >= 0 && $current_hour < 6;
	}

	public static function get_payment_status_via_cron() {
		if ( self::is_exact_cron_quiet_hours() ) {
			return;
		}

		// execute get_payment_status of ExactOnlineAjax class.
		$ajax = new ExactOnlineAjax();
		$ajax->get_payment_status();
	}

	/**
	 * Process the payment status of the order via Exact Online.
	 *
	 * @param array $
	 * @return void
	 */
	public static function cmbird_payment_status() {
		if ( self::is_exact_cron_quiet_hours() ) {
			return;
		}

		$args     = func_get_args();
		$order_id = $args[0];
		if ( empty( $order_id ) ) {
			return;
		}
		$order             = wc_get_order( $order_id );
		$object            = array();
		$order_id          = $order->get_id();
		$object['OrderID'] = $order_id;
		$customer_id       = $order->get_customer_id();
		// get the eo_account_id from the user meta.
		$object['AccountID'] = get_user_meta( $customer_id, 'eo_account_id', true );
		$response            = ( new Connector() )->payment_status( $object );
		// check response contains "Payment_Status" key.
		if ( ! isset( $response['Payment_Status'] ) ) {
			return;
		}
		// if response is Paid then update the order status to completed.
		if ( 'Paid' === $response['Payment_Status'] ) {
			// set order as paid.
			if ( $order->get_status() === 'completed' ) {
				return;
			}
			$order->payment_complete();
			$order->update_status( 'completed', __( 'Payment processed in Exact Online', 'commercebird' ) );
			$order->save();
		} elseif ( 'Unpaid' === $response['Payment_Status'] ) {
			if ( $order->get_status() === 'on-hold' ) {
				return;
			}
			$order->update_status( 'on-hold', __( 'Payment not processed in Exact Online', 'commercebird' ) );
			$order->save();
		}
	}

	/**
	 * Check if the image exists in the media library.
	 *
	 * @param string $picture_name
	 * @return int|false Attachment ID if exists, false otherwise
	 */
	private static function get_existing_image_id( $picture_name ) {
		global $wpdb;

		// Strip extension from picture_name.
		$picture_title = pathinfo( $picture_name, PATHINFO_FILENAME );

		// Prepare a LIKE match to catch sanitized versions.
		$like = '%' . $wpdb->esc_like( $picture_title ) . '%';

		$attachment_id = $wpdb->get_var(
			$wpdb->prepare(
				"
            SELECT ID FROM {$wpdb->posts}
            WHERE post_type = 'attachment'
            AND post_title LIKE %s
            ORDER BY ID DESC
            LIMIT 1
            ",
				$like
			)
		);

		return $attachment_id ? (int) $attachment_id : false;
	}

	/**
	 * Upload the product image from Exact Online.
	 *
	 * @param string $picture_url URL of the image to upload.
	 * @param string $picture_name Name of the image file (used for naming the attachment).
	 * @return int|false Attachment ID of the uploaded image if successful, otherwise false
	 */
	private static function upload_image( $picture_url, $picture_name ) {
		require_once ABSPATH . 'wp-admin/includes/media.php';
		require_once ABSPATH . 'wp-admin/includes/file.php';
		require_once ABSPATH . 'wp-admin/includes/image.php';

		// Fetch image content.
		$response = wp_safe_remote_get( $picture_url, array( 'timeout' => 10 ) );

		if ( is_wp_error( $response ) ) {
			// error_log( 'Error fetching image: ' . $response->get_error_message() );
			return false;
		}

		$image_data = wp_remote_retrieve_body( $response );
		if ( empty( $image_data ) ) {
			return false;
		}

		// Generate filename.
		$upload_dir = wp_upload_dir();
		$filename   = sanitize_file_name( $picture_name );
		$file_path  = $upload_dir['path'] . '/' . $filename;

		// Save the file.
		file_put_contents( $file_path, $image_data );

		// Check if file was saved correctly.
		if ( ! file_exists( $file_path ) ) {
			return false;
		}

		// Prepare file array for WordPress.
		$file = array(
			'name'     => $filename,
			'type'     => mime_content_type( $file_path ),
			'tmp_name' => $file_path,
			'size'     => filesize( $file_path ),
		);

		// Upload to WordPress Media Library.
		$attachment_id = media_handle_sideload( $file, 0 );

		// Check for errors.
		if ( is_wp_error( $attachment_id ) ) {
			// error_log( 'Error attaching image: ' . $attachment_id->get_error_message() );
			return false;
		}

		return $attachment_id;
	}

	/**
	 * Sync orders via cron job.
	 * This function will get all orders from the last 30 days that have no eo_order_id or eo_invoice_id as meta key
	 * and send them to the send_orders function in CommerceBird class.
	 */
	public static function sync_orders_via_cron() {
		if ( self::is_exact_cron_quiet_hours() ) {
			return;
		}

		$logger  = wc_get_logger();
		$context = array( 'source' => 'cmbird_exact_online_orders' );
		// get all orders from the last 30 days that have no eo_order_id or eo_invoice_id as meta key.
		$start_date = gmdate( 'Y-m-d H:i:s', strtotime( '-15 days' ) );
		$end_date   = time();
		$orders     = wc_get_orders(
			array(
				'status'       => array_diff( array_keys( wc_get_order_statuses() ), array( 'wc-failed', 'wc-checkout-draft', 'wc-pending', 'wc-on-hold', 'wc-cancelled' ) ),
				'limit'        => -1,
				'date_created' => $start_date . '...' . $end_date,
				'return'       => 'ids',
				'meta_query'   => array(
					array(
						'key'     => 'eo_order_id',
						'compare' => 'NOT EXISTS',
					),
				),
			)
		);
		if ( empty( $orders ) ) {
			return;
		}
		// send the orders to the send_orders function in CommerceBird class.
		$response = ( new Connector() )->send_orders( array( 'orderIds' => $orders ) );
		if ( is_string( $response ) || 200 !== $response['code'] ) {
			// log the error.
			$logger->error(
				'Error syncing Exact Online orders',
				$context + array(
					'response' => $response,
					'orders'   => $orders,
				)
			);
			return;
		}
	}
}
