<?php
/**
 * Connection Handler
 *
 * @category   WordPress_Plugin
 * @package    Common
 * @subpackage Handlers\Rest_API
 * @author     miniOrange <info@miniorange.com>
 * @license    MIT/Expat
 * @link       https://plugins.miniorange.com/woocommerce-api-product-sync-with-woocommerce-rest-apis
 */

namespace MOWCPS\Common\Handlers\Rest_API;

use WP_Error;
use MOWCPS\Common\Models\Connection_Model;
use MOWCPS\Common\Models\Supplier_Model;
use MOWCPS\Common\DTO\Connection_Request;
use MOWCPS\Common\Constants\Constants;
use MOWCPS\Common\Utils\Model_Utils;
use MOWCPS\Common\Services\API\API_Service;
use MOWCPS\Common\Models\Authorization_Model;
use MOWCPS\Common\Services\Authorization\Authorization_Service;
use MOWCPS\Common\Utils\Utils;
use MOWCPS\Common\Services\ReadFileData\Read_File_Data;
use MOWCPS\Common\Services\Cache\Cache_Service;
use MOWCPS\Common\Services\FTP\FTP_Service;
use MOWCPS\Common\Models\Sync_Details_DAO;
use MOWCPS\Common\Models\Setting_Model;

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

/**
 * Class Connection_Handler
 *
 * Handles connection REST API operations
 */
class Connection_Handler {

	const CACHE_KEY_PREFIX = 'mowcps_connections_supplier_';

	/**
	 * Constructor
	 */
	public function __construct() {
	}

	/**
	 * Get all connections for a supplier
	 *
	 * @param string $supplier_uuid Supplier UUID.
	 * @param array  $params Query parameters.
	 * @return array|WP_Error
	 */
	public function get_all_by_supplier_uuid( $supplier_uuid, $params = array() ) {
		// Process column selection if provided.
		if ( isset( $params['columns'] ) ) {
			$params = Connection_Request::validate_parameters( $params );
			if ( is_wp_error( $params ) ) {
				return $params;
			}
		}

		$cache_key = self::CACHE_KEY_PREFIX . $supplier_uuid;

		return Cache_Service::remember( $cache_key, Cache_Service::DEFAULT_EXPIRATION, function () use ( $supplier_uuid, $params ) {
			$connections = Connection_Model::get_by_supplier_uuid( $supplier_uuid, $params );
			if ( is_wp_error( $connections ) ) {
				return $connections;
			}
			return Model_Utils::sanitize_for_api_response( $connections );
		} );
	}

	/**
	 * Get connection by UUID
	 *
	 * @param string $connection_uuid Connection UUID.
	 * @return array|WP_Error
	 */
	public function get_by_uuid( $connection_uuid ) {
		// Validate UUID parameter using DTO.
		$validation = Connection_Request::validate_uuid( $connection_uuid, 'connection_uuid' );
		if ( is_wp_error( $validation ) ) {
			return $validation;
		}

		$connection = Connection_Model::get_by_uuid( $connection_uuid );
		if ( ! $connection ) {
			return new WP_Error(
				Constants::ERROR_CONNECTION_NOT_FOUND,
				Constants::MSG_CONNECTION_NOT_FOUND,
				array( 'status' => Constants::HTTP_NOT_FOUND )
			);
		}

		// Remove ID fields from API response and format data types.
		return Model_Utils::sanitize_for_api_response( $connection );
	}

	/**
	 * Create connection for a supplier
	 *
	 * @param string|int $supplier_identifier Supplier UUID or ID.
	 * @param array      $data Connection data.
	 * @return array|WP_Error
	 */
	public function create( $supplier_identifier, $data ) {
		$identifier_info = Model_Utils::identify_identifier( $supplier_identifier );
		if ( is_wp_error( $identifier_info ) ) {
			return $identifier_info;
		}

		if ( 'id' === $identifier_info['type'] ) {
			$supplier_id = $identifier_info['value'];
		} else {
			$supplier = Supplier_Model::get_by_uuid( $identifier_info['value'] );
			if ( ! $supplier ) {
				return new WP_Error(
					Constants::ERROR_SUPPLIER_NOT_FOUND,
					Constants::MSG_SUPPLIER_NOT_FOUND,
					array( 'status' => Constants::HTTP_NOT_FOUND )
				);
			}
			$supplier_id = $supplier['id'];
		}

		// Transform and validate data using DTO.
		$connection_request = Connection_Request::from_request_parameters( $data );
		if ( is_wp_error( $connection_request ) ) {
			return $connection_request;
		}

		// Add supplier_id to the validated data.
		$validated_data                = $connection_request->to_array();
		$validated_data['supplier_id'] = $supplier_id;

		$result = Connection_Model::create( $validated_data );
		if ( is_wp_error( $result ) ) {
			return $result;
		}

		Cache_Service::forget( self::CACHE_KEY_PREFIX . $supplier_identifier );

		// Remove ID fields from API response and format data types.
		return Model_Utils::sanitize_for_api_response( $result );
	}

	/**
	 * Update connection
	 *
	 * @param string $connection_uuid Connection UUID.
	 * @param array  $data Connection data.
	 * @return array|WP_Error
	 */
	public function update( $connection_uuid, $data ) {
		// Validate UUID parameter using DTO.
		$validation = Connection_Request::validate_uuid( $connection_uuid, 'connection_uuid' );
		if ( is_wp_error( $validation ) ) {
			return $validation;
		}

		$supplier_uuid = $this->resolve_supplier_uuid( $connection_uuid );

		// Transform and validate data using DTO for partial updates.
		if ( ! empty( $data ) ) {
			$connection_request = Connection_Request::from_update_parameters( $data );
			if ( is_wp_error( $connection_request ) ) {
				return $connection_request;
			}
			$data = $connection_request->to_array();
		}

		$result = Connection_Model::update( $connection_uuid, $data );
		if ( is_wp_error( $result ) ) {
			return $result;
		}

		if ( $supplier_uuid ) {
			Cache_Service::forget( self::CACHE_KEY_PREFIX . $supplier_uuid );
		}

		// Remove ID fields from API response and format data types.
		return Model_Utils::sanitize_for_api_response( $result );
	}

	/**
	 * Delete connection
	 *
	 * @param string $connection_uuid Connection UUID.
	 * @return bool|WP_Error
	 */
	public function delete( $connection_uuid ) {
		// Validate UUID parameter using DTO.
		$validation = Connection_Request::validate_uuid( $connection_uuid, 'connection_uuid' );
		if ( is_wp_error( $validation ) ) {
			return $validation;
		}

		$supplier_uuid = $this->resolve_supplier_uuid( $connection_uuid );

		$result = Connection_Model::delete( $connection_uuid );

		if ( ! is_wp_error( $result ) && $supplier_uuid ) {
			Cache_Service::forget( self::CACHE_KEY_PREFIX . $supplier_uuid );
		}

		return $result;
	}

	/**
	 * Resolve a connection UUID to its parent supplier UUID.
	 *
	 * @param string $connection_uuid Connection UUID.
	 * @return string|false Supplier UUID or false if unresolvable.
	 */
	private function resolve_supplier_uuid( $connection_uuid ) {
		$connection = Connection_Model::get_by_uuid( $connection_uuid );
		if ( ! $connection || empty( $connection['supplier_id'] ) ) {
			return false;
		}

		$supplier = Supplier_Model::get_by_id( $connection['supplier_id'] );

		return $supplier ? $supplier['uuid'] : false;
	}

	/**
	 * Test file upload for a connection
	 *
	 * Validates connection and file type, then either saves new file content or reads
	 * from an existing file. Processes the file using Read_File_Data service and returns
	 * formatted file data for the frontend.
	 *
	 * @param string     $connection_uuid Connection UUID.
	 * @param array|null $request_body Request body containing file_content. If null or empty, reads from existing file.
	 * @return array|WP_Error Formatted file data on success, WP_Error on failure.
	 */
	public function test_file_upload( $connection_uuid, $request_body ) {

		// Validate UUID parameter using DTO.
		$validation = Connection_Request::validate_uuid( $connection_uuid, 'connection_uuid' );
		if ( is_wp_error( $validation ) ) {
			return $validation;
		}

		$connection = Connection_Model::get_by_uuid( $connection_uuid );
		if ( ! $connection ) {
			return new WP_Error(
				Constants::ERROR_CONNECTION_NOT_FOUND,
				Constants::MSG_CONNECTION_NOT_FOUND,
				array( 'status' => Constants::HTTP_NOT_FOUND )
			);
		}

		// Save file using Read_File_Data service.
		$file_save_directory = MOWCPS_PLUGIN_DIR . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'external-supplier' . DIRECTORY_SEPARATOR . 'file-response';

		// File extension for filename and detection.
		$file_extension = strtolower( $connection['configuration']['file_type'] ?? '' );

		// Determine upload mode (manual upload vs existing file read).
		$is_manual_upload = ( null !== $request_body && ! empty( $request_body['file_content'] ) );

		if ( empty( $request_body ) || empty( $request_body['file_content'] ) ) {
			// Read from existing file, no file upload involved.

			// Validate file_name and file_type using DTO.
			$validation = Connection_Request::validate_file_connection_config( $connection['configuration'], false );
			if ( is_wp_error( $validation ) ) {
				return $validation;
			}

			// Re-extract file_extension after validation to ensure it's set correctly.
			$file_extension = strtolower( $connection['configuration']['file_type'] );

			$file_name = $connection['configuration']['file_name'] . '.' . $file_extension;
			$file_path = $file_save_directory . DIRECTORY_SEPARATOR . $file_name;

			// phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionNameInvalid -- wp_normalize_path is WordPress core.
			$file_path = wp_normalize_path( $file_path );

			if ( ! file_exists( $file_path ) || ! is_readable( $file_path ) ) {
				return new WP_Error(
					'file_not_found',
					sprintf( 'File "%s" does not exist or is not readable.', $file_name ),
					array(
						'status'    => Constants::HTTP_NOT_FOUND,
						'file_name' => $file_name,
						'file_path' => $file_path,
					)
				);
			}

			$file_content = Utils::retrieve_data_from_file( $file_path );

			if ( null === $file_content || false === $file_content ) {
				// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- fallback.
				$file_content = file_get_contents( $file_path );
			}

			if ( false === $file_content ) {
				return new WP_Error(
					'file_read_error',
					sprintf( 'Unable to read file "%s". Please check file permissions.', $file_name ),
					array(
						'status'    => Constants::HTTP_INTERNAL_SERVER_ERROR,
						'file_name' => $file_name,
						'file_path' => $file_path,
					)
				);
			}

			// Remove UTF-8 BOM if present.
			if ( substr( $file_content, 0, 3 ) === "\xEF\xBB\xBF" ) {
				$file_content = substr( $file_content, 3 );
			}
		} else {
			// Get file content from request.
			$file_content = $request_body['file_content'] ?? '';
			// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_stripcslashes
			$file_content = stripcslashes( $file_content );
		}

		if ( $is_manual_upload ) {
			$validation = Connection_Request::validate_file_connection_config( $connection['configuration'], true );
			if ( is_wp_error( $validation ) ) {
				return $validation;
			}

			// Re-extract file_extension after validation to ensure it's set correctly.
			$file_extension = strtolower( $connection['configuration']['file_type'] );
		}

		// Add BOM only for new uploads in appropriate file types.
		if ( $is_manual_upload && in_array( $file_extension, array( Constants::CSV_FILE, Constants::TSV_FILE, Constants::TXT_FILE, Constants::TEXT_FILE ), true ) ) {
			$file_content = "\xEF\xBB\xBF" . $file_content; // phpcs:ignore WordPress.WP.I18n.NoEmptyStrings
		}

		if ( $is_manual_upload ) {
			$save_result = Utils::store_data_in_file(
				$connection['configuration']['file_name'] . '.' . $file_extension,
				$file_save_directory,
				$file_content,
			);

			if ( ! $save_result ) {
				return new WP_Error(
					'file_save_failed',
					'Failed to save file. Please check file permissions.',
					array( 'status' => Constants::HTTP_INTERNAL_SERVER_ERROR )
				);
			}
		}

		$file_data = Read_File_Data::read_data_from_file(
			$file_save_directory,
			$connection['configuration']['file_name'] . '.' . $file_extension,
			1,
			10,
			$connection['configuration']['use_first_row_as_header'],
			$connection['configuration']['delimiter']
		);

		if ( is_array( $file_data ) ) {
			$response_data = $file_data;
		} else {
			return new WP_Error(
				'file_processing_failed',
				'Failed to save or process file. Please check file permissions and try again.',
				array( 'status' => Constants::HTTP_BAD_REQUEST )
			);
		}

		// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Debugging test file upload.
		Utils::store_data_in_file(
			'test_file_upload_' . $connection['id'] . '.json',
			MOWCPS_PLUGIN_DIR . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'test-results',
			wp_json_encode( $response_data, true ),
		);

		return $response_data;
	}

	/**
	 * Test connection
	 *
	 * Tests a connection based on its type. For FILE connections, handles file upload/reading.
	 * For API/FTP connections, makes API calls to test connectivity.
	 *
	 * @param string     $connection_uuid Connection UUID.
	 * @param array|null $request_body Optional request body. Used for FILE connections to pass file_content.
	 * @return array|WP_Error
	 */
	public function test_connection( $connection_uuid, $request_body = null ) {
		// Validate UUID parameter using DTO.
		$validation = Connection_Request::validate_uuid( $connection_uuid, 'connection_uuid' );
		if ( is_wp_error( $validation ) ) {
			return $validation;
		}

		$connection = Connection_Model::get_by_uuid( $connection_uuid );
		if ( ! $connection ) {
			return new WP_Error(
				Constants::ERROR_CONNECTION_NOT_FOUND,
				Constants::MSG_CONNECTION_NOT_FOUND,
				array( 'status' => Constants::HTTP_NOT_FOUND )
			);
		}

		// Route to appropriate test handler based on connection type.
		$connection_type = $connection['type'] ?? '';
		switch ( $connection_type ) {
			case Constants::CONNECTION_TYPE_FILE:
				return $this->test_file_upload( $connection_uuid, $request_body );
			case Constants::CONNECTION_TYPE_API:
				return $this->test_api_connection( $connection_uuid );
			case Constants::CONNECTION_TYPE_FTP:
				return $this->test_ftp_connection( $connection );
			// case Constants::CONNECTION_TYPE_SFTP:
				// return $this->test_sftp_connection( $connection_uuid );	
			default:
				return $this->test_api_connection( $connection_uuid );
		}
	}

	/**
	 * Test FTP connection
	 *
	 * Makes FTP calls to test connectivity for FTP connections.
	 *
	 * @param array $connection Connection config.
	 * @return array|WP_Error
	 */
	public function test_ftp_connection( $connection ) {
		if ( ! $connection ) {
			return new WP_Error(
				Constants::ERROR_CONNECTION_NOT_FOUND,
				Constants::MSG_CONNECTION_NOT_FOUND,
				array( 'status' => Constants::HTTP_NOT_FOUND )
			);
		}

		$connection_file_settings = Setting_Model::get_by_connection_and_operation( $connection['id'], 'file-sync', array( 'configuration' => true ) );

		$host      = $connection['configuration']['host'];
		$username  = $connection['configuration']['username'];
		$password  = $connection['configuration']['password'];
		$port      = $connection['configuration']['port'];
		$filename  = $connection_file_settings['configuration']['file_name'];
		$delimiter = $connection_file_settings['configuration']['delimiter'] ?? false;

		$remote_file = $connection['configuration']['file_path'];
		$file_path   = MOWCPS_PLUGIN_DIR . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'external-supplier' . DIRECTORY_SEPARATOR . 'api-file-response' . DIRECTORY_SEPARATOR . $connection['id'] . DIRECTORY_SEPARATOR . $remote_file;
		$file_path   = wp_normalize_path( $file_path );

		$protocol = ( 22 === (int) $port ) ? 'sftp' : 'ftp';

		$file_directory = MOWCPS_PLUGIN_DIR . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'external-supplier' . DIRECTORY_SEPARATOR . 'api-file-response' . DIRECTORY_SEPARATOR . $connection['id'];
		// Retry mechanism settings.
		$retry_attempts  = 5;  // Number of retry attempts for file download.
		$timeout_seconds = 120;  // Timeout in seconds for FTP/SFTP operations.
		$connect         = false;
		if ( 'sftp' === $protocol ) {
			for ( $attempt = 1; $attempt <= $retry_attempts; $attempt++ ) {
				$connect = FTP_Service::connect_and_login_sftp( $host, $username, $password, $timeout_seconds, $port );
				if ( ! $connect ) {
					throw new \Exception( 'SFTP connection or login failed.' );
				}

				if ( FTP_Service::download_file_sftp( $connect, $remote_file, $file_path ) ) {
					FTP_Service::extract_zip( $file_path, $file_directory );
					$use_first_row_as_header = $connection['configuration']['use_first_row_as_header'] ?? true;
					$data                    = Read_File_Data::read_data_from_file( $file_directory, $filename, 0, 10, $use_first_row_as_header, $delimiter );
					return $data;
				}
			}
			return array(
				'status'  => Constants::HTTP_BAD_REQUEST,
				'message' => 'SFTP file download unsuccessful',
			);
		} else {
			for ( $attempt = 1; $attempt <= $retry_attempts; $attempt++ ) {
				$connect = FTP_Service::connect_and_login_ftp( $host, $username, $password, $timeout_seconds, $port );
				if ( ! $connect ) {
					throw new \Exception( 'FTP connection or login failed on attempt ' );
				}

				$file_path = wp_normalize_path( $file_path );
				if ( ftp_get( $connect, $file_path, $remote_file, FTP_BINARY ) ) {
					FTP_Service::extract_zip( $file_path, $file_directory );
					$use_first_row_as_header = $connection['configuration']['use_first_row_as_header'] ?? true;
					$data                    = Read_File_Data::read_data_from_file( $file_directory, $filename, 1, 10, $use_first_row_as_header, $delimiter );
					return $data;
				}

			}
			return array(
				'status'  => Constants::HTTP_BAD_REQUEST,
				'message' => 'FTP file download unsuccessful',
			);
		}

		return array(
			'status' => Constants::HTTP_OK,
			'message' => 'FTP connection successful',
		);
	}

	/**
	 * Test API/FTP connection
	 *
	 * Makes API calls to test connectivity for API and FTP connections.
	 *
	 * @param string $connection_uuid Connection UUID.
	 * @return array|WP_Error
	 */
	private function test_api_connection( $connection_uuid ) {
		$connection = Connection_Model::get_by_uuid( $connection_uuid );
		if ( ! $connection ) {
			return new WP_Error(
				Constants::ERROR_CONNECTION_NOT_FOUND,
				Constants::MSG_CONNECTION_NOT_FOUND,
				array( 'status' => Constants::HTTP_NOT_FOUND )
			);
		}

		$supplier_id = $connection['supplier_id'];

		if ( $connection['configuration']['auth_enabled'] ) {
			$authorization = Authorization_Model::get_by_supplier_id( $supplier_id );

			$auth_services = new Authorization_Service();
			$header        = $auth_services->generate_headers( $authorization, $supplier_id );
		} else {
			$header = array();
		}

		if ( is_wp_error( $header ) ) {
			return $header;
		}

		// Process cookies if they exist and convert them to a Cookie header.
		if ( isset( $header['set-cookie'] ) ) {
			$cookies = $header['set-cookie'];
			if ( ! is_array( $cookies ) ) {
				$cookies = array( $cookies );
			}

			$cookie_string = '';
			foreach ( $cookies as $cookie ) {
				// Extract just the cookie name and value, ignore attributes.
				if ( preg_match( '/^([^=]+=[^;]+)/', $cookie, $matches ) ) {
					$cookie_string .= $matches[1] . '; ';
				}
			}

			$cookie_string = rtrim( $cookie_string, '; ' );
			unset( $header['set-cookie'] );
			if ( ! empty( $cookie_string ) ) {
				$header['Cookie'] = $cookie_string;
			}
		}

		$connection_config = $connection['configuration'];

		$arguments = array(
			'method'  => $connection_config['method'],
			'headers' => array_merge( $connection_config['headers'], $header ),
			'timeout' => 600,
		);

		if ( Constants::GET_REQUEST !== $connection_config['method'] ) {
			$body_type = $connection_config['body']['type'];
			$body_data = $connection_config['body']['data'];
			if ( Constants::JSON === $body_type ) {
				$arguments['body'] = json_encode( $body_data );
			} elseif ( ! empty( $body_data ) ) {
				$arguments['body'] = $body_data;
			}
		}

		$response = API_Service::make_api_call( $connection_config['endpoint'], $arguments );

		$content_type = wp_remote_retrieve_header( $response, 'content-type' );
		$raw_body     = wp_remote_retrieve_body( $response );

		$supplier = Supplier_Model::get_by_id( $supplier_id );

		// Parse response based on content type.
		if ( strpos( $content_type, 'application/xml' ) !== false || strpos( $content_type, 'text/xml' ) !== false ) {
			$response_body = API_Service::parse_xml( $raw_body );
			$response_body = json_decode( $response_body, true );

			if ( Constants::SUPPLIER_APP_TYPE_BARRON === $supplier['app_type'] ) {
				$response_body = json_decode( $response_body['ResponseData'], true );
			}
		} elseif ( strpos( $content_type, 'application/json' ) !== false ) {
			$response_body = json_decode( $raw_body, true );
		} else {
			// Try to auto-detect format.
			$trimmed_body = trim( $raw_body );
			if ( strpos( $trimmed_body, '<?xml' ) === 0 || strpos( $trimmed_body, '<' ) === 0 ) {
				$response_body = API_Service::parse_xml( $raw_body );
				$response_body = json_decode( $response_body, true );
			} else {
				$response_body = json_decode( $raw_body, true );
			}
		}

		if ( is_wp_error( $response ) ) {
			return $response;
		}

		Utils::store_data_in_file( 'test_result_' . $connection['id'] . '.json', MOWCPS_PLUGIN_DIR . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'test-results', wp_json_encode( $response_body, true ), true );

		$response['body'] = $response_body;
		return $response;
	}

	/**
	 * Handle chunked file upload
	 *
	 * Processes large file uploads by receiving chunks sequentially, saving them to temporary
	 * files, and combining them when all chunks are received. For files larger than 50MB,
	 * this method handles the upload in chunks to avoid memory limitations and timeout issues.
	 *
	 * @param string $connection_uuid Connection UUID.
	 * @param array  $request_body Request body containing chunk data (chunk_number, total_chunks, chunk_data, file_name, file_size).
	 * @return array|WP_Error Response array with message and data on success, WP_Error on failure.
	 */
	public function handle_chunked_upload( $connection_uuid, $request_body ) {
		// Validate UUID parameter using DTO.
		$validation = Connection_Request::validate_uuid( $connection_uuid, 'connection_uuid' );
		if ( is_wp_error( $validation ) ) {
			return $validation;
		}

		// Validate connection exists.
		$connection = $this->get_by_uuid( $connection_uuid );
		if ( is_wp_error( $connection ) ) {
			return $connection;
		}

		// Get connection with ID field for directory path (get_by_uuid in handler may remove ID).
		$connection_with_id = Connection_Model::get_by_uuid( $connection_uuid );
		if ( ! $connection_with_id ) {
			return new WP_Error(
				Constants::ERROR_CONNECTION_NOT_FOUND,
				Constants::MSG_CONNECTION_NOT_FOUND,
				array( 'status' => Constants::HTTP_NOT_FOUND )
			);
		}

		// Sanitize required fields from multipart/form-data.
		$chunk_number = intval( $request_body['chunk_number'] ?? 0 );
		$total_chunks = intval( $request_body['total_chunks'] ?? 0 );
		$file_name    = sanitize_file_name( $request_body['file_name'] ?? '' );
		$file_size    = intval( $request_body['file_size'] ?? 0 );
		$chunk_file   = $request_body['chunk_file'] ?? null;

		// Validate chunked upload data.
		if ( $chunk_number <= 0 ) {
			return new WP_Error(
				Constants::ERROR_INVALID_CHUNK_DATA,
				'Invalid chunk number. Chunk number must be a positive integer.',
				array( 'status' => Constants::HTTP_BAD_REQUEST )
			);
		}

		if ( $total_chunks <= 0 ) {
			return new WP_Error(
				Constants::ERROR_INVALID_CHUNK_DATA,
				'Invalid total chunks. Total chunks must be a positive integer.',
				array( 'status' => Constants::HTTP_BAD_REQUEST )
			);
		}

		if ( $chunk_number > $total_chunks ) {
			return new WP_Error(
				Constants::ERROR_INVALID_CHUNK_NUMBER,
				sprintf( 'Chunk number %d exceeds total chunks %d.', $chunk_number, $total_chunks ),
				array( 'status' => Constants::HTTP_BAD_REQUEST )
			);
		}

		if ( empty( $file_name ) ) {
			return new WP_Error(
				Constants::ERROR_INVALID_CHUNK_DATA,
				'File name is required.',
				array( 'status' => Constants::HTTP_BAD_REQUEST )
			);
		}

		if ( $file_size <= 0 ) {
			return new WP_Error(
				Constants::ERROR_INVALID_CHUNK_DATA,
				'File size must be a positive integer.',
				array( 'status' => Constants::HTTP_BAD_REQUEST )
			);
		}

		// Validate uploaded file.
		if ( ! $chunk_file || ! isset( $chunk_file['tmp_name'] ) || ! is_uploaded_file( $chunk_file['tmp_name'] ) ) {
			return new WP_Error(
				Constants::ERROR_INVALID_CHUNK_DATA,
				'Chunk file is required and must be a valid uploaded file.',
				array( 'status' => Constants::HTTP_BAD_REQUEST )
			);
		}

		// Get temporary chunk directory for files > 50MB.
		$temp_chunk_directory = MOWCPS_PLUGIN_DIR . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'external-supplier' . DIRECTORY_SEPARATOR . 'temp' . DIRECTORY_SEPARATOR . $connection_with_id['id'];

		// Ensure temporary directory exists.
		if ( ! wp_mkdir_p( $temp_chunk_directory ) ) {
			return new WP_Error(
				'directory_creation_failed',
				'Failed to create temporary chunk directory.',
				array( 'status' => Constants::HTTP_INTERNAL_SERVER_ERROR )
			);
		}

		// Get final file save directory (same as regular file uploads).
		$final_file_directory = MOWCPS_PLUGIN_DIR . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'external-supplier' . DIRECTORY_SEPARATOR . 'file-response';

		// Ensure final directory exists.
		if ( ! wp_mkdir_p( $final_file_directory ) ) {
			return new WP_Error(
				'directory_creation_failed',
				'Failed to create final file directory.',
				array( 'status' => Constants::HTTP_INTERNAL_SERVER_ERROR )
			);
		}

		// Move uploaded chunk file to temporary directory (no base64 decoding needed).
		$chunk_file_name = 'chunk_' . $chunk_number . '.tmp';
		$chunk_file_path = $temp_chunk_directory . DIRECTORY_SEPARATOR . $chunk_file_name;
		$chunk_file_path = wp_normalize_path( $chunk_file_path );

		// Move uploaded file to destination.
		if ( ! move_uploaded_file( $chunk_file['tmp_name'], $chunk_file_path ) ) {
			return new WP_Error(
				'chunk_save_failed',
				sprintf( 'Failed to save chunk %d.', $chunk_number ),
				array( 'status' => Constants::HTTP_INTERNAL_SERVER_ERROR )
			);
		}

		// Check if all chunks are uploaded.
		$all_chunks_uploaded = true;
		for ( $i = 1; $i <= $total_chunks; $i++ ) {
			$chunk_file = $temp_chunk_directory . DIRECTORY_SEPARATOR . 'chunk_' . $i . '.tmp';
			if ( ! file_exists( $chunk_file ) ) {
				$all_chunks_uploaded = false;
				break;
			}
		}

		// If all chunks are uploaded, combine them.
		if ( $all_chunks_uploaded ) {
			// Determine file extension from file name.
			$file_extension = pathinfo( $file_name, PATHINFO_EXTENSION );
			if ( empty( $file_extension ) ) {
				$file_extension = Constants::TXT_FILE;
			}

			$file_name_without_ext = pathinfo( $file_name, PATHINFO_FILENAME );

			// Prepare final file path.
			$final_file_path = $final_file_directory . DIRECTORY_SEPARATOR . $file_name_without_ext . '.' . $file_extension;
			$final_file_path = wp_normalize_path( $final_file_path );

			// Delete existing file if it exists to avoid lock issues.
			if ( file_exists( $final_file_path ) ) {
				// Use wp_delete_file() for WordPress-compliant file deletion.
				wp_delete_file( $final_file_path );
			}

			// Open final file for writing in binary mode.
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- Using streams for memory-efficient file operations.
			$final_handle = fopen( $final_file_path, 'wb' );
			if ( false === $final_handle ) {
				return new WP_Error(
					'file_creation_failed',
					'Failed to create final file for chunk combination.',
					array( 'status' => Constants::HTTP_INTERNAL_SERVER_ERROR )
				);
			}

			// Add BOM for CSV/TSV/TXT files (same as regular uploads).
			if ( in_array( strtolower( $file_extension ), array( Constants::CSV_FILE, Constants::TSV_FILE, Constants::TXT_FILE ), true ) ) {
				// phpcs:ignore WordPress.WP.I18n.NoEmptyStrings -- BOM byte sequence for UTF-8 encoding.
				fwrite( $final_handle, "\xEF\xBB\xBF" );
			}

			// Combine all chunks using stream copy to avoid loading entire file into memory.
			for ( $i = 1; $i <= $total_chunks; $i++ ) {
				$chunk_file = $temp_chunk_directory . DIRECTORY_SEPARATOR . 'chunk_' . $i . '.tmp';
				if ( ! file_exists( $chunk_file ) ) {
					fclose( $final_handle );
					// Use wp_delete_file() for WordPress-compliant file deletion.
					wp_delete_file( $final_file_path );
					return new WP_Error(
						'missing_chunk',
						sprintf( 'Chunk %d file not found.', $i ),
						array( 'status' => Constants::HTTP_INTERNAL_SERVER_ERROR )
					);
				}

				// Open the chunk file in read binary mode.
				// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- Using streams for memory-efficient file operations.
				$chunk_handle = fopen( $chunk_file, 'rb' );
				if ( false === $chunk_handle ) {
					fclose( $final_handle );
					// Use wp_delete_file() for WordPress-compliant file deletion.
					wp_delete_file( $final_file_path );
					return new WP_Error(
						'chunk_read_failed',
						sprintf( 'Failed to open chunk %d for reading.', $i ),
						array( 'status' => Constants::HTTP_INTERNAL_SERVER_ERROR )
					);
				}

				// Pipe the data directly from chunk file to final file.
				// This bypasses PHP memory almost entirely.
				$bytes_copied = stream_copy_to_stream( $chunk_handle, $final_handle );
				fclose( $chunk_handle );

				if ( false === $bytes_copied ) {
					fclose( $final_handle );
					// Use wp_delete_file() for WordPress-compliant file deletion.
					wp_delete_file( $final_file_path );
					return new WP_Error(
						'chunk_copy_failed',
						sprintf( 'Failed to copy chunk %d to final file.', $i ),
						array( 'status' => Constants::HTTP_INTERNAL_SERVER_ERROR )
					);
				}

				// Delete chunk file after successful copy.
				// Use wp_delete_file() for WordPress-compliant file deletion.
				wp_delete_file( $chunk_file );
			}

			// Close final file handle.
			fclose( $final_handle );

			// Verify final file was created successfully.
			if ( ! file_exists( $final_file_path ) ) {
				// Clean up temporary chunk directory before returning error to prevent stale files.
				$this->cleanup_temp_directory( $temp_chunk_directory );
				return new WP_Error(
					'file_combination_failed',
					'Failed to combine chunks into final file.',
					array( 'status' => Constants::HTTP_INTERNAL_SERVER_ERROR )
				);
			}

			// Update connection configuration with file name.
			$connection['configuration']['file_name'] = $file_name_without_ext;
			$connection['configuration']['file_type'] = strtolower( $file_extension );

			// Test the file connection with the combined file.
			$test_result = $this->test_file_upload( $connection_uuid, array() );

			if ( is_wp_error( $test_result ) ) {
				// Clean up temporary chunk directory before returning error to prevent stale files.
				$this->cleanup_temp_directory( $temp_chunk_directory );
				return $test_result;
			}

			// Clean up temporary chunk directory after successful merge.
			$this->cleanup_temp_directory( $temp_chunk_directory );

			// Return success message.
			return array(
				'message' => 'completed all chunks',
				'data'    => $test_result,
			);
		}

		// Return progress message.
		return array(
			'message' => 'chunk received',
			'chunk'   => $chunk_number,
			'total'   => $total_chunks,
		);
	}

	/**
	 * Clean up temporary directory and its contents
	 *
	 * Recursively removes all files and directories within the specified temporary directory.
	 *
	 * @param string $temp_directory Path to the temporary directory to clean up.
	 * @return void
	 * @private
	 */
	private function cleanup_temp_directory( $temp_directory ) {
		global $wp_filesystem;

		if ( ! is_dir( $temp_directory ) ) {
			return;
		}

		if ( ! \WP_Filesystem() ) {
			// Fallback to manual cleanup if WP_Filesystem fails.
			$files = glob( $temp_directory . DIRECTORY_SEPARATOR . '*' );
			if ( is_array( $files ) ) {
				foreach ( $files as $file ) {
					if ( is_file( $file ) ) {
						\wp_delete_file( $file );
					} elseif ( is_dir( $file ) ) {
						$dir_files = glob( $file . DIRECTORY_SEPARATOR . '*' );
						if ( is_array( $dir_files ) ) {
							foreach ( $dir_files as $dir_file ) {
								if ( is_file( $dir_file ) ) {
									\wp_delete_file( $dir_file );
								}
							}
						}
						// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.file_system_operations_rmdir -- Fallback cleanup.
						@rmdir( $file );
					}
				}
			}
			// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.file_system_operations_rmdir -- Fallback cleanup.
			@rmdir( $temp_directory );
			return;
		}

		// Normalize path for Windows compatibility.
		$temp_directory = \wp_normalize_path( $temp_directory );

		// Use WP_Filesystem to recursively delete the directory and all its contents.
		// The second parameter (true) enables recursive deletion.
		if ( $wp_filesystem->exists( $temp_directory ) ) {
			$wp_filesystem->delete( $temp_directory, true );
		}

		// Delete the entire temp folder if it's empty.
		$temp_folder = MOWCPS_PLUGIN_DIR . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'external-supplier' . DIRECTORY_SEPARATOR . 'temp';
		$temp_folder = \wp_normalize_path( $temp_folder );
		if ( is_dir( $temp_folder ) ) {
			$temp_files = glob( $temp_folder . DIRECTORY_SEPARATOR . '*' );
			if ( is_array( $temp_files ) && empty( $temp_files ) ) {
				// Use WP_Filesystem to delete empty temp folder.
				if ( $wp_filesystem->exists( $temp_folder ) ) {
					$wp_filesystem->delete( $temp_folder, true );
				}
			}
		}
	}
}
