<?php

namespace WC_Affiliate\API;

defined( 'ABSPATH' ) || exit;

use Exception;
use WC_Affiliate\Models\Affiliate;
use WC_Affiliate\Models\ImportJob;
use WC_Affiliate\Models\Referral;
use WC_Affiliate\Models\Settings;
use WC_Affiliate\Models\Transaction;
use WC_Affiliate\Models\User;
use WC_Affiliate\Models\Visit;
use WC_Affiliate\Traits\Auth;
use WC_Affiliate\Traits\Cleaner;
use WC_Affiliate\Traits\Rest;
use WC_Affiliate\Traits\Validation;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;

/**
 * Class Import
 *
 * Handles the import operations for various data types, including affiliates, visits, referrals, transactions, and settings.
 * Provides API endpoints to get available import types, validate import files, start import jobs,
 * retrieve job status, cancel jobs, and delete job details.
 *
 * @since 1.0.0
 */
class Import {

	use Auth;

	use Rest;
	use Validation;

	/**
	 * WordPress Filesystem instance
	 *
	 * @since 1.0.0
	 * @var mixed
	 */
	protected $filesystem;

	/**
	 * Initialize WordPress Filesystem
	 *
	 * Initializes the WordPress Filesystem API for secure file operations.
	 *
	 * @since 1.0.0
	 *
	 * @return bool True if filesystem initialized successfully
	 */
	protected function init_filesystem(): bool {
		global $wp_filesystem;

		if ( ! function_exists( 'WP_Filesystem' ) ) {
			require_once ABSPATH . 'wp-admin/includes/file.php';
		}

		if ( ! WP_Filesystem() ) {
			return false;
		}

		$this->filesystem = $wp_filesystem;
		return true;
	}

	/**
	 * Read file content using WordPress Filesystem
	 *
	 * Reads file content securely using WordPress Filesystem API.
	 *
	 * @since 1.0.0
	 *
	 * @param string $file_path Absolute file path.
	 *
	 * @return string|WP_Error File content or error. object on failure
	 */
	protected function read_file( string $file_path ) {
		if ( ! $this->init_filesystem() ) {
			return new WP_Error( 'filesystem_error', __( 'Unable to initialize WordPress Filesystem', 'wc-affiliate' ) );
		}

		if ( ! $this->filesystem->exists( $file_path ) ) {
			return new WP_Error( 'file_not_found', __( 'File does not exist', 'wc-affiliate' ) );
		}

		$content = $this->filesystem->get_contents( $file_path );
		if ( false === $content ) {
			return new WP_Error( 'read_error', __( 'Failed to read file content', 'wc-affiliate' ) );
		}

		return $content;
	}

	/**
	 * Write file content using WordPress Filesystem
	 *
	 * Writes content to file securely using WordPress Filesystem API.
	 *
	 * @since 1.0.0
	 *
	 * @param string $file_path Absolute file path.
	 * @param string $content   Content to write.
	 *
	 * @return bool|WP_Error True on success or error object on failure
	 */
	protected function write_file( string $file_path, string $content ) {
		if ( ! $this->init_filesystem() ) {
			return new WP_Error( 'filesystem_error', __( 'Unable to initialize WordPress Filesystem', 'wc-affiliate' ) );
		}

		$result = $this->filesystem->put_contents( $file_path, $content );
		if ( false === $result ) {
			return new WP_Error( 'write_error', __( 'Failed to write file content', 'wc-affiliate' ) );
		}

		return true;
	}

	/**
	 * Delete file using WordPress Filesystem
	 *
	 * Deletes file securely using WordPress Filesystem API.
	 *
	 * @since 1.0.0
	 *
	 * @param string $file_path Absolute file path.
	 *
	 * @return bool|WP_Error True on success or error. object on failure
	 */
	protected function delete_file( string $file_path ) {
		if ( ! $this->init_filesystem() ) {
			return new WP_Error( 'filesystem_error', __( 'Unable to initialize WordPress Filesystem', 'wc-affiliate' ) );
		}

		if ( ! $this->filesystem->exists( $file_path ) ) {
			return new WP_Error( 'file_not_found', __( 'File does not exist', 'wc-affiliate' ) );
		}

		$result = $this->filesystem->delete( $file_path );
		if ( false === $result ) {
			return new WP_Error( 'delete_error', __( 'Failed to delete file', 'wc-affiliate' ) );
		}

		return true;
	}

	/**
	 * Check if file exists using WordPress Filesystem
	 *
	 * Checks file existence securely using WordPress Filesystem API.
	 *
	 * @since 1.0.0
	 *
	 * @param string $file_path Absolute file path.
	 *
	 * @return bool True if file exists.
	 */
	protected function file_exists( string $file_path ): bool {
		if ( ! $this->init_filesystem() ) {
			return false;
		}

		return $this->filesystem->exists( $file_path );
	}

	/**
	 * Create directory using WordPress Filesystem
	 *
	 * Creates directory securely using WordPress Filesystem API.
	 *
	 * @since 1.0.0
	 *
	 * @param string $dir_path Absolute directory path.
	 *
	 * @return bool|WP_Error True on success or error. object on failure
	 */
	protected function create_directory( string $dir_path ) {
		if ( ! $this->init_filesystem() ) {
			return new WP_Error( 'filesystem_error', __( 'Unable to initialize WordPress Filesystem', 'wc-affiliate' ) );
		}

		if ( $this->filesystem->is_dir( $dir_path ) ) {
			return true; // Directory already exists.
		}

		$result = $this->filesystem->mkdir( $dir_path );
		if ( false === $result ) {
			return new WP_Error( 'mkdir_error', __( 'Failed to create directory', 'wc-affiliate' ) );
		}

		return true;
	}

	/**
	 * Move uploaded file using WordPress Filesystem
	 *
	 * Moves uploaded files securely using WordPress Filesystem API.
	 *
	 * @since 1.0.0
	 *
	 * @param string $source_path Source file path (uploaded temp file).
	 * @param string $target_path Target file path.
	 *
	 * @return bool|WP_Error True on success or error object on failure.
	 */
	protected function move_uploaded_file( string $source_path, string $target_path ) {
		if ( ! $this->init_filesystem() ) {
			return new WP_Error( 'filesystem_error', __( 'Unable to initialize WordPress Filesystem', 'wc-affiliate' ) );
		}

		// For uploaded files, we still need to use PHP's move_uploaded_file initially.
		// Then we can work with the file using WP Filesystem.
		if ( ! move_uploaded_file( $source_path, $target_path ) ) {
			return new WP_Error( 'move_error', __( 'Failed to move uploaded file', 'wc-affiliate' ) );
		}

		return true;
	}

	/**
	 * Upload file for import.
	 *
	 * @since 1.0.0
	 *
	 * @param WP_REST_Request $request The REST request object containing file upload data.
	 *
	 * @return WP_REST_Response|WP_Error The response with upload result.
	 */
	public function upload_file( WP_REST_Request $request ) {
		$files = $request->get_file_params();

		if ( empty( $files['file'] ) ) {
			return $this->response_error( __( 'No file uploaded', 'wc-affiliate' ), 400 );
		}

		$file = $files['file'];

		// Validate file upload error.
		if ( UPLOAD_ERR_OK !== $file['error'] ) {
			$error_messages = array(
				UPLOAD_ERR_INI_SIZE   => __( 'File size exceeds server limit', 'wc-affiliate' ),
				UPLOAD_ERR_FORM_SIZE  => __( 'File size exceeds form limit', 'wc-affiliate' ),
				UPLOAD_ERR_PARTIAL    => __( 'File was only partially uploaded', 'wc-affiliate' ),
				UPLOAD_ERR_NO_FILE    => __( 'No file was uploaded', 'wc-affiliate' ),
				UPLOAD_ERR_NO_TMP_DIR => __( 'Missing temporary folder', 'wc-affiliate' ),
				UPLOAD_ERR_CANT_WRITE => __( 'Failed to write file to disk', 'wc-affiliate' ),
				UPLOAD_ERR_EXTENSION  => __( 'File upload stopped by extension', 'wc-affiliate' ),
			);
			$message = $error_messages[ $file['error'] ] ?? __( 'File upload error', 'wc-affiliate' );
			return $this->response_error( $message, 400 );
		}

		// Check file size (max 10MB, but allow filter override).
		$max_size = apply_filters( 'wc_affiliate_import_max_file_size', 10 * 1024 * 1024 ); // 10MB default
		if ( $file['size'] > $max_size ) {
			return $this->response_error(
				sprintf(
					/* translators: %s: maximum file size */
					__( 'File size too large. Maximum size is %s.', 'wc-affiliate' ),
					size_format( $max_size )
				),
				400
			);
		}

		// Check minimum file size (prevent empty files).
		$min_size = apply_filters( 'wc_affiliate_import_min_file_size', 1 ); // 1 byte minimum
		if ( $file['size'] < $min_size ) {
			return $this->response_error( __( 'File is too small or empty', 'wc-affiliate' ), 400 );
		}

		// Get file extension and validate it.
		$extension = strtolower( pathinfo( $file['name'], PATHINFO_EXTENSION ) );
		$allowed_extensions = apply_filters( 'wc_affiliate_import_allowed_extensions', array( 'csv', 'json', 'xml' ) );

		if ( ! in_array( $extension, $allowed_extensions, true ) ) {
			return $this->response_error(
				sprintf(
					/* translators: %s: allowed file extensions */
					__( 'Invalid file type. Only %s files are allowed.', 'wc-affiliate' ),
					implode( ', ', $allowed_extensions )
				),
				400
			);
		}

		// Check MIME type for additional security.
		$allowed_mime_types = apply_filters(
			'wc_affiliate_import_allowed_mime_types',
			array(
				'csv'  => array( 'text/csv', 'text/plain', 'application/csv', 'application/vnd.ms-excel' ),
				'json' => array( 'application/json', 'text/plain' ),
				'xml'  => array( 'text/xml', 'application/xml', 'application/rss+xml' ),
			)
		);

		$file_mime_type = $file['type'];
		$valid_mime_type = isset( $allowed_mime_types[ $extension ] ) && in_array( $file_mime_type, $allowed_mime_types[ $extension ], true );

		// If MIME type doesn't match, try to detect it from file content.
		if ( ! $valid_mime_type && function_exists( 'finfo_open' ) ) {
			$finfo = finfo_open( FILEINFO_MIME_TYPE );
			if ( $finfo ) {
				$detected_mime = finfo_file( $finfo, $file['tmp_name'] );
				finfo_close( $finfo );
				$valid_mime_type = isset( $allowed_mime_types[ $extension ] ) && in_array( $detected_mime, $allowed_mime_types[ $extension ], true );
			}
		}

		if ( ! $valid_mime_type ) {
			return $this->response_error( __( 'File type validation failed', 'wc-affiliate' ), 400 );
		}

		// Create upload directory if it doesn't exist.
		$upload_dir = wp_upload_dir();
		$import_dir = $upload_dir['basedir'] . '/wc-affiliate-imports';

		if ( ! $this->file_exists( $import_dir ) ) {
			$mkdir_result = $this->create_directory( $import_dir );
			if ( is_wp_error( $mkdir_result ) ) {
				return $this->response_error( $mkdir_result->get_error_message(), 500 );
			}
		}

		// Generate unique filename.
		$filename  = 'import-' . time() . '-' . wp_generate_uuid4() . '.' . pathinfo( $file['name'], PATHINFO_EXTENSION );
		$file_path = $import_dir . '/' . $filename;

		// Move uploaded file using filesystem method.
		$move_result = $this->move_uploaded_file( $file['tmp_name'], $file_path );
		if ( is_wp_error( $move_result ) ) {
			return $this->response_error( $move_result->get_error_message(), 500 );
		}

		// Return file URL.
		$file_url = $upload_dir['baseurl'] . '/wc-affiliate-imports/' . $filename;

		return $this->response_success(
			array(
				'url'  => $file_url,
				'path' => $file_path,
				'name' => $file['name'],
				'size' => $file['size'],
			)
		);
	}

	/**
	 * Get available import types
	 *
	 * Retrieves all supported import data types and their configurations.
	 *
	 * @since 1.0.0
	 *
	 * @return WP_REST_Response Response containing import types data
	 */
	public function get_types() {
		return $this->response_success( wca_get_import_types() );
	}

	/**
	 * Validate import file
	 *
	 * Validates import file format and content before processing.
	 *
	 * @since 1.0.0
	 *
	 * @param WP_REST_Request $request REST request object.
	 *
	 * @return WP_REST_Response|WP_Error Response with validation results or error
	 */
	public function validate( WP_REST_Request $request ) {
		$import_type = sanitize_text_field( $request->get_param( 'type' ) );
		$file_url    = sanitize_text_field( $request->get_param( 'file_url' ) );

		// Validate import type.
		if ( ! array_key_exists( $import_type, wca_get_import_types() ) ) {
			return $this->response_error( __( 'Invalid import type', 'wc-affiliate' ), 400 );
		}

		// Download and validate file.
		$validation_result = $this->validate_file( $file_url, $import_type );
		if ( is_wp_error( $validation_result ) ) {
			return $this->response_error( $validation_result->get_error_message(), 400 );
		}

		return $this->response_success( $validation_result );
	}

	/**
	 * Start data import
	 *
	 * Initiates import job for validated file and options.
	 *
	 * @since 1.0.0
	 *
	 * @param WP_REST_Request $request REST request object.
	 *
	 * @return WP_REST_Response|WP_Error Response with job details or error
	 * @throws Exception If import initialization fails.
	 */
	public function start( WP_REST_Request $request ) {
		$import_type = sanitize_text_field( $request->get_param( 'type' ) );
		$file_url    = sanitize_text_field( $request->get_param( 'file_url' ) );
		$options     = $request->get_param( 'options' );

		// Validate import type.
		if ( ! array_key_exists( $import_type, wca_get_import_types() ) ) {
			return $this->response_error( __( 'Invalid import type', 'wc-affiliate' ), 400 );
		}

		// Allow filtering import options before starting.
		$options = apply_filters( 'wc_affiliate_import_options', $options, $import_type, $file_url );

		// Generate import ID.
		$import_id = wp_generate_uuid4();

		// Validate and prepare import data.
		$validation_result = $this->validate_file( $file_url, $import_type );
		if ( is_wp_error( $validation_result ) ) {
			return $this->response_error( $validation_result->get_error_message(), 400 );
		}

		// Allow filtering validation result.
		$validation_result = apply_filters( 'wc_affiliate_import_validation_result', $validation_result, $import_type, $file_url );

		// Store import job details.
		$job_data = array(
			'id'            => $import_id,
			'type'          => $import_type,
			'file_url'      => $file_url,
			'options'       => $options,
			'total_items'   => $validation_result['total_items'],
			'valid_items'   => $validation_result['valid_items'],
			'invalid_items' => $validation_result['invalid_items'],
			'status'        => 'pending',
			'progress'      => 0,
			'imported'      => 0,
			'skipped'       => 0,
			'errors'        => array(),
			'created_at'    => time(),
			'created_by'    => get_current_user_id(),
		);

		update_option( "wc_affiliate_import_job_{$import_id}", $job_data );

		// Schedule background processing for large datasets.
		if ( $validation_result['total_items'] > 500 ) {
			wp_schedule_single_event( time(), 'wc_affiliate_process_import', array( $import_id ) );
		} else {
			// Process immediately for small datasets.
			$this->process( $import_id );
		}

		$response = array(
			'import_id'     => $import_id,
			'status'        => 'initiated',
			'total_items'   => $validation_result['total_items'],
			'valid_items'   => $validation_result['valid_items'],
			'invalid_items' => $validation_result['invalid_items'],
			'message'       => $validation_result['total_items'] > 500 ?
				__( 'Import job queued for background processing', 'wc-affiliate' ) :
				__( 'Import processing started', 'wc-affiliate' ),
		);

		return $this->response_success( $response );
	}

	/**
	 * Get import status
	 *
	 * Retrieves current status and progress of an import job.
	 *
	 * @since 1.0.0
	 *
	 * @param WP_REST_Request $request REST request object.
	 *
	 * @return WP_REST_Response|WP_Error Response with job status or error
	 */
	public function get_status( WP_REST_Request $request ) {
		$import_id = sanitize_text_field( $request->get_param( 'import_id' ) );

		$job_data = get_option( "wc_affiliate_import_job_{$import_id}" );
		if ( ! $job_data ) {
			return $this->response_error( __( 'Import job not found', 'wc-affiliate' ), 404 );
		}

		return $this->response_success( $job_data );
	}

	/**
	 * Cancel import job
	 *
	 * Cancels a running or pending import job.
	 *
	 * @since 1.0.0
	 *
	 * @param WP_REST_Request $request REST request object.
	 *
	 * @return WP_REST_Response|WP_Error Response with cancellation confirmation or error
	 */
	public function cancel( WP_REST_Request $request ) {
		$import_id = sanitize_text_field( $request->get_param( 'import_id' ) );

		$job_data = get_option( "wc_affiliate_import_job_{$import_id}" );
		if ( ! $job_data ) {
			return $this->response_error( __( 'Import job not found', 'wc-affiliate' ), 404 );
		}

		if ( 'processing' === $job_data['status'] ) {
			return $this->response_error( __( 'Cannot cancel job that is currently processing', 'wc-affiliate' ), 400 );
		}

		// Update status to cancelled.
		$job_data['status']       = 'cancelled';
		$job_data['cancelled_at'] = time();
		update_option( "wc_affiliate_import_job_{$import_id}", $job_data );

		return $this->response_success( null, __( 'Import cancelled successfully', 'wc-affiliate' ) );
	}

	/**
	 * Delete import job
	 *
	 * Removes import job data from the system.
	 *
	 * @since 1.0.0
	 *
	 * @param WP_REST_Request $request REST request object.
	 *
	 * @return WP_REST_Response|WP_Error Response with deletion confirmation or error
	 */
	public function delete( WP_REST_Request $request ) {
		$import_id = sanitize_text_field( $request->get_param( 'import_id' ) );

		$job_data = get_option( "wc_affiliate_import_job_{$import_id}" );
		if ( ! $job_data ) {
			return $this->response_error( __( 'Import job not found', 'wc-affiliate' ), 404 );
		}

		// Delete job data.
		delete_option( "wc_affiliate_import_job_{$import_id}" );

		return $this->response_success( null, __( 'Import deleted successfully', 'wc-affiliate' ) );
	}

	/**
	 * Get list of import jobs
	 *
	 * Retrieves all import jobs with their current status.
	 *
	 * @since 1.0.0
	 *
	 * @return WP_REST_Response Response containing import jobs data
	 */
	public function get_jobs() {
		$import_jobs = ImportJob::get_jobs();

		return $this->response_success( $import_jobs );
	}

	/**
	 * Process import job
	 *
	 * Executes the import job, processing data and updating progress.
	 *
	 * @since 1.0.0
	 *
	 * @param string $import_id Import job ID.
	 *
	 * @return bool True on successful completion
	 * @throws Exception On processing failure.
	 */
	public function process( string $import_id ): bool {
		// Validate import_id
		if ( empty( $import_id ) || ! is_string( $import_id ) ) {
			return false;
		}

		$job_data = get_option( "wc_affiliate_import_job_{$import_id}" );
		if ( ! $job_data || ! is_array( $job_data ) ) {
			return false;
		}

		try {
			// Update status to processing.
			$job_data['status']     = 'processing';
			$job_data['started_at'] = time();
			$result = update_option( "wc_affiliate_import_job_{$import_id}", $job_data );

			if ( ! $result ) {
				throw new Exception( __( 'Failed to update job status to processing', 'wc-affiliate' ) );
			}

			// Validate file URL exists and is accessible
			if ( empty( $job_data['file_url'] ) ) {
				throw new Exception( __( 'Import file URL is missing', 'wc-affiliate' ) );
			}

			// Download and parse import file with timeout protection
			$import_data = $this->parse_import_file( $job_data['file_url'] );
			if ( is_wp_error( $import_data ) ) {
				throw new Exception( $import_data->get_error_message() );
			}

			if ( empty( $import_data ) ) {
				throw new Exception( __( 'Import file contains no valid data', 'wc-affiliate' ) );
			}

			// Process import data.
			$result = $this->import_data( $job_data['type'], $import_data, $job_data['options'] ?? array(), $import_id );

			if ( ! is_array( $result ) ) {
				throw new Exception( __( 'Import processing failed - invalid result format', 'wc-affiliate' ) );
			}

			// Update job with completion data.
			$job_data['status']       = 'completed';
			$job_data['progress']     = 100;
			$job_data['completed_at'] = time();
			$job_data['imported']     = $result['imported'] ?? 0;
			$job_data['skipped']      = $result['skipped'] ?? 0;
			$job_data['errors']       = $result['errors'] ?? array();

			$update_result = update_option( "wc_affiliate_import_job_{$import_id}", $job_data );
			if ( ! $update_result ) {
				wca_log( "Warning: Failed to update completed job data for import {$import_id}" );
			}

			return true;
		} catch ( Exception $e ) {
			// Update status to failed.
			$job_data['status']    = 'failed';
			$job_data['error']     = $e->getMessage();
			$job_data['failed_at'] = time();

			update_option( "wc_affiliate_import_job_{$import_id}", $job_data );
			wca_log( "Import job {$import_id} failed: " . $e->getMessage() );

			return false;
		}
	}

	/**
	 * Validate import file
	 *
	 * @param string $file_url    File URL.
	 * @param string $import_type Import type.
	 *
	 * @return array|WP_Error
	 */
	protected function validate_file( string $file_url, string $import_type ) {
		// Download file.
		$response = wp_remote_get( $file_url, array( 'timeout' => 30 ) );
		if ( is_wp_error( $response ) ) {
			return new WP_Error( 'download_failed', __( 'Failed to download import file', 'wc-affiliate' ) );
		}

		$file_content = wp_remote_retrieve_body( $response );
		if ( empty( $file_content ) ) {
			return new WP_Error( 'empty_file', __( 'Import file is empty', 'wc-affiliate' ) );
		}

		// Parse file content.
		$import_data = $this->parse_file_content( $file_content, $file_url );
		if ( is_wp_error( $import_data ) ) {
			return $import_data;
		}

		// Validate data structure.
		return $this->validate_import_data( $import_data, $import_type );
	}

	/**
	 * Parse file content
	 *
	 * @param string $content  File content.
	 * @param string $file_url File URL.
	 *
	 * @return array|WP_Error
	 */
	protected function parse_file_content( string $content, string $file_url ) {
		$file_extension = pathinfo( $file_url, PATHINFO_EXTENSION );

		switch ( strtolower( $file_extension ) ) {
			case 'json':
				$data = json_decode( $content, true );
				if ( json_last_error() !== JSON_ERROR_NONE ) {
					return new WP_Error( 'invalid_json', __( 'Invalid JSON format', 'wc-affiliate' ) );
				}

				return $data;

			case 'csv':
				return $this->parse_csv_content( $content );

			case 'xml':
				return $this->parse_xml_content( $content );

			default:
				return new WP_Error( 'unsupported_format', __( 'Unsupported file format', 'wc-affiliate' ) );
		}
	}

	/**
	 * Parse CSV content
	 *
	 * @param string $content CSV content.
	 *
	 * @return array
	 */
	protected function parse_csv_content( string $content ): array {
		// Use memory-efficient streaming for large files
		$handle = fopen( 'php://temp', 'r+' );
		fwrite( $handle, $content );
		rewind( $handle );

		$data = array();
		$headers = array();

		// Read headers
		if ( ( $header_line = fgetcsv( $handle ) ) !== false ) {
			$headers = $header_line;
		}

		if ( empty( $headers ) ) {
			fclose( $handle );
			return $data;
		}

		// Process rows one by one to save memory
		while ( ( $row_data = fgetcsv( $handle ) ) !== false ) {
			// Skip empty rows
			if ( empty( array_filter( $row_data ) ) ) {
				continue;
			}

			$row = array();
			foreach ( $headers as $index => $header ) {
				$value = $row_data[ $index ] ?? '';

				// Only try to decode JSON for non-empty values to save processing time
				if ( ! empty( $value ) && ( $value[0] === '{' || $value[0] === '[' ) ) {
					$decoded = json_decode( $value, true );
					if ( json_last_error() === JSON_ERROR_NONE ) {
						$value = $decoded;
					}
				}

				$row[ $header ] = $value;
			}
			$data[] = $row;

			// Prevent memory exhaustion for extremely large files
			if ( count( $data ) >= apply_filters( 'wc_affiliate_import_max_rows_preview', 10000 ) ) {
				break;
			}
		}

		fclose( $handle );
		return $data;
	}

	/**
	 * Parse XML content
	 *
	 * @param string $content XML content.
	 *
	 * @return array|WP_Error
	 */
	protected function parse_xml_content( string $content ) {
		try {
			$xml = new \SimpleXMLElement( $content );

			return json_decode( wp_json_encode( $xml ), true );
		} catch ( Exception $e ) {
			return new WP_Error( 'invalid_xml', __( 'Invalid XML format', 'wc-affiliate' ) );
		}
	}

	/**
	 * Parse import file
	 *
	 * @param string $file_url File URL.
	 *
	 * @return array|WP_Error
	 */
	protected function parse_import_file( string $file_url ) {
		$response = wp_remote_get( $file_url, array( 'timeout' => 30 ) );
		if ( is_wp_error( $response ) ) {
			return $response;
		}

		$file_content = wp_remote_retrieve_body( $response );

		return $this->parse_file_content( $file_content, $file_url );
	}

	/**
	 * Validate import data
	 *
	 * @param array  $data        Import data.
	 * @param string $import_type Import type.
	 *
	 * @return array
	 */
	protected function validate_import_data( array $data, string $import_type ): array {
		$import_types      = wca_get_import_types();
		$type_config       = $import_types[ $import_type ] ?? array();
		$total_items       = count( $data );
		$valid_items       = 0;
		$invalid_items     = 0;
		$validation_errors = array();

		foreach ( $data as $index => $item ) {
			$item_errors = array();

			// Check required fields.
			foreach ( $type_config['required_fields'] as $field ) {
				if ( empty( $item[ $field ] ) ) {
					/* translators: %s is the field name */
					$item_errors[] = sprintf( __( 'Missing required field: %s', 'wc-affiliate' ), $field );
				}
			}

			// Validate field types and values.
			$field_validation = $this->validate_item_fields( $item, $import_type );
			if ( ! empty( $field_validation ) ) {
				$item_errors = array_merge( $item_errors, $field_validation );
			}

			if ( empty( $item_errors ) ) {
				++$valid_items;
			} else {
				++$invalid_items;
				$validation_errors[ $index ] = $item_errors;
			}
		}

		return array(
			'total_items'   => $total_items,
			'valid_items'   => $valid_items,
			'invalid_items' => $invalid_items,
			'errors'        => $validation_errors,
		);
	}

	/**
	 * Validate item fields
	 *
	 * @param array  $item        Item data.
	 * @param string $import_type Import type.
	 *
	 * @return array
	 */
	protected function validate_item_fields( array $item, string $import_type ): array {
		$errors = array();

		switch ( $import_type ) {
			case 'affiliates':
				if ( ! empty( $item['user_email'] ) && ! is_email( $item['user_email'] ) ) {
					$errors[] = __( 'Invalid email format', 'wc-affiliate' );
				}
				if ( ! empty( $item['status'] ) && ! in_array(
					$item['status'],
					array(
						'active',
						'pending',
						'rejected',
						'suspended',
					),
					true
				) ) {
					$errors[] = __( 'Invalid status value', 'wc-affiliate' );
				}
				break;

			case 'visits':
				if ( ! empty( $item['affiliate_id'] ) && ! is_numeric( $item['affiliate_id'] ) ) {
					$errors[] = __( 'Invalid affiliate ID', 'wc-affiliate' );
				}
				if ( ! empty( $item['page_url'] ) && ! filter_var( $item['page_url'], FILTER_VALIDATE_URL ) ) {
					$errors[] = __( 'Invalid page URL', 'wc-affiliate' );
				}
				break;

			case 'referrals':
				if ( ! empty( $item['affiliate_id'] ) && ! is_numeric( $item['affiliate_id'] ) ) {
					$errors[] = __( 'Invalid affiliate ID', 'wc-affiliate' );
				}
				if ( ! empty( $item['commission'] ) && ! is_numeric( $item['commission'] ) ) {
					$errors[] = __( 'Invalid commission amount', 'wc-affiliate' );
				}
				break;

			case 'transactions':
				if ( ! empty( $item['affiliate_id'] ) && ! is_numeric( $item['affiliate_id'] ) ) {
					$errors[] = __( 'Invalid affiliate ID', 'wc-affiliate' );
				}
				if ( ! empty( $item['amount'] ) && ! is_numeric( $item['amount'] ) ) {
					$errors[] = __( 'Invalid amount', 'wc-affiliate' );
				}
				break;
		}

		return $errors;
	}

	/**
	 * Import data
	 *
	 * @param string $import_type Import type.
	 * @param array  $data        Import data.
	 * @param array  $options     Import options.
	 * @param string $import_id   Import ID.
	 *
	 * @return array
	 */
	protected function import_data( string $import_type, array $data, array $options, string $import_id ): array {
		$imported = 0;
		$skipped  = 0;
		$errors   = array();

		/**
		 * Filter import data before processing.
		 *
		 * Allows modification of import data before processing items.
		 *
		 * @since 1.0.0
		 *
		 * @param array  $data        Import data array.
		 * @param string $import_type Import type.
		 * @param array  $options     Import options.
		 * @param string $import_id   Import job ID.
		 */
		$data = apply_filters( 'wc_affiliate_import_data_before_processing', $data, $import_type, $options, $import_id );

		$total_items = count( $data );
		$progress_interval = max( 1, intval( $total_items / 100 ) ); // Update progress max 100 times

		foreach ( $data as $index => $item ) {
			try {
				$result = $this->import_single_item( $import_type, $item, $options );
				if ( $result ) {
					++$imported;
				} else {
					++$skipped;
				}

				// Update progress at intervals to improve performance
				if ( $index % $progress_interval === 0 || $index === $total_items - 1 ) {
					$job_data = get_option( "wc_affiliate_import_job_{$import_id}" );
					if ( $job_data ) {
						$job_data['progress'] = round( ( ( $index + 1 ) / $total_items ) * 100 );
						update_option( "wc_affiliate_import_job_{$import_id}", $job_data );
					}
				}
			} catch ( Exception $e ) {
				$errors[ $index ] = $e->getMessage();
				++$skipped;
			}
		}

		return array(
			'imported' => $imported,
			'skipped'  => $skipped,
			'errors'   => $errors,
		);
	}

	/**
	 * Import single item
	 *
	 * @param string $import_type Import type.
	 * @param array  $item        Item data.
	 * @param array  $options     Import options.
	 *
	 * @return bool
	 * @throws Exception Throw exception on failure.
	 */
	protected function import_single_item( string $import_type, array $item, array $options ): bool {
		switch ( $import_type ) {
			case 'affiliates':
				return $this->import_affiliate( $item, $options );

			case 'visits':
				return $this->import_visit( $item, $options );

			case 'referrals':
				return $this->import_referral( $item, $options );

			case 'transactions':
				return $this->import_transaction( $item, $options );

			case 'settings':
				return $this->import_settings( $item, $options );

			default:
				return false;
		}
	}

	/**
	 * Import affiliate
	 *
	 * @param array $item    Affiliate data.
	 * @param array $options Import options.
	 *
	 * @return bool
	 * @throws Exception Throw exception on failure.
	 */
	protected function import_affiliate( array $item, array $options ): bool {
		// Check if user already exists.
		$user = get_user_by( 'email', $item['user_email'] );

		if ( ! $user ) {
			// Create new user if not exists.
			$user_data = array(
				'user_login'   => $item['user_login'] ?? sanitize_user( $item['user_email'] ),
				'user_email'   => $item['user_email'],
				'display_name' => $item['display_name'] ?? '',
				'role'         => 'customer',
			);

			$user_id = User::create_user( $user_data );
			if ( is_wp_error( $user_id ) ) {
				throw new Exception( $user_id->get_error_message() ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
			}
		} else {
			$user_id = $user->ID;

			// Skip if user is already an affiliate and update is not forced.
			if ( empty( $options['force_update'] ) && wca_is_active_affiliate( $user_id ) ) {
				return false;
			}
		}

		// Create affiliate.
		$affiliate_model = new Affiliate();
		$affiliate_data  = array(
			'user_id'           => $user_id,
			'status'            => $item['status'] ?? 'pending',
			'commission_type'   => $item['commission_type'] ?? 'percent',
			'commission_amount' => $item['commission_amount'] ?? 10,
			'payment_method'    => $item['payment_method'] ?? '',
			'payment_details'   => $item['payment_details'] ?? '',
			'auto_payout'       => $item['auto_payout'] ?? 0,
			'campaign'          => $item['campaign'] ?? '',
		);

		$result = $affiliate_model->create( $affiliate_data );

		return ! is_wp_error( $result );
	}

	/**
	 * Import visit
	 *
	 * @param array $item    Visit data.
	 * @param array $options Import options.
	 *
	 * @return bool
	 */
	protected function import_visit( array $item, array $options ): bool {
		$visit_model = new Visit();

		$visit_data = array(
			'affiliate'    => $item['affiliate_id'],
			'page_url'     => $item['page_url'],
			'referrer_url' => $item['referrer_url'] ?? '',
			'campaign'     => $item['campaign'] ?? '',
			'ip'           => $item['ip_address'] ?? wca_get_client_ip(),
			'time'         => isset( $item['visited_at'] ) ? strtotime( $item['visited_at'] ) : time(),
		);

		$result = $visit_model->create( $visit_data );

		return ! is_wp_error( $result );
	}

	/**
	 * Import referral
	 *
	 * @param array $item    Referral data.
	 * @param array $options Import options.
	 *
	 * @return bool
	 */
	protected function import_referral( array $item, array $options ): bool {
		$referral_model = new Referral();

		$referral_data = array(
			'affiliate'      => $item['affiliate_id'],
			'type'           => $item['type'] ?? 'sale',
			'visit'          => $item['visit_id'] ?? 0,
			'order_id'       => $item['order_id'],
			'products'       => $item['products'] ?? '',
			'order_total'    => $item['order_total'],
			'commission'     => $item['commission'],
			'payment_status' => $item['payment_status'] ?? 'pending',
			'transaction_id' => $item['transaction_id'] ?? 0,
			'time'           => isset( $item['created_at'] ) ? strtotime( $item['created_at'] ) : time(),
		);

		$result = $referral_model->create( $referral_data );

		return ! is_wp_error( $result );
	}

	/**
	 * Import transaction
	 *
	 * @param array $item    Transaction data.
	 * @param array $options Import options.
	 *
	 * @return bool
	 */
	protected function import_transaction( array $item, array $options ): bool {
		$transaction_model = new Transaction();

		$transaction_data = array(
			'affiliate'      => $item['affiliate_id'],
			'amount'         => $item['amount'],
			'payment_method' => $item['payment_method'],
			'txn_id'         => $item['txn_id'] ?? '',
			'status'         => $item['status'] ?? 'pending',
			'request_at'     => isset( $item['requested_at'] ) ? strtotime( $item['requested_at'] ) : time(),
			'process_at'     => isset( $item['processed_at'] ) ? strtotime( $item['processed_at'] ) : null,
		);

		$result = $transaction_model->create( $transaction_data );

		return ! is_wp_error( $result );
	}

	/**
	 * Import settings
	 *
	 * @param array $item    Settings data.
	 * @param array $options Import options.
	 *
	 * @return bool
	 */
	protected function import_settings( array $item, array $options ): bool {
		$option_model = new Settings();

		// Get legacy to new settings mapping from reusable function.
		$mappings = wca_get_legacy_settings_mappings();

		$migrated = false;

		// Migrate each legacy option group.
		foreach ( $mappings as $legacy_option => $mapping ) {
			if ( isset( $item[ $legacy_option ] ) && is_array( $item[ $legacy_option ] ) ) {
				foreach ( $mapping as $legacy_key => $new_field_path ) {
					if ( isset( $item[ $legacy_option ][ $legacy_key ] ) ) {
						// Split the field path into group and nested path.
						$path_parts  = explode( '.', $new_field_path, 2 );
						$group       = $path_parts[0];
						$nested_path = $path_parts[1];

						$option_model->set_option( $group, $nested_path, $item[ $legacy_option ][ $legacy_key ] );
						$migrated = true;
					}
				}
			}
		}

		return $migrated;
	}

}
