<?php
/**
 * Data import service for handling various import formats and previews.
 *
 * @package RewardsWP
 */

namespace Awesomemotive\Rewardswp\Services;

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

use WP_Error;

/**
 * Generic data import service for handling various data formats.
 *
 * Designed to be multipurpose and not limited to CSV or points.
 * Can be extended for JSON, XML, and other data formats in the future.
 *
 * @since 1.0.0
 */
class DataImportService {

	/**
	 * Get the maximum upload file size based on PHP configuration.
	 *
	 * @since 1.0.0
	 *
	 * @return int Maximum file size in bytes.
	 */
	public function get_max_upload_size(): int {
		$upload_max   = $this->parse_size( ini_get( 'upload_max_filesize' ) );
		$post_max     = $this->parse_size( ini_get( 'post_max_size' ) );
		$memory_limit = $this->parse_size( ini_get( 'memory_limit' ) );

		// Return the smallest limit.
		return min( $upload_max, $post_max, $memory_limit );
	}

	/**
	 * Get the maximum upload file size in MB for display.
	 *
	 * @since 1.0.0
	 *
	 * @return int Maximum file size in MB.
	 */
	public function get_max_upload_size_mb(): int {
		return (int) floor( $this->get_max_upload_size() / ( 1024 * 1024 ) );
	}

	/**
	 * Parse a PHP size value (like '8M', '2G') into bytes.
	 *
	 * @since 1.0.0
	 *
	 * @param string $size The size string to parse.
	 *
	 * @return int Size in bytes.
	 */
	private function parse_size( string $size ): int {
		$size = trim( $size );
		$last = strtolower( $size[ strlen( $size ) - 1 ] );
		$size = (int) $size;

		switch ( $last ) {
			case 'g':
				$size *= 1024;
				// Fall through.
			case 'm':
				$size *= 1024;
				// Fall through.
			case 'k':
				$size *= 1024;
		}

		return $size;
	}

	/**
	 * Validate uploaded file based on type, size, and other criteria.
	 *
	 * @since 1.0.0
	 *
	 * @param array $file         The $_FILES array element.
	 * @param array $allowed_types Allowed file extensions (e.g., ['csv', 'json']).
	 *
	 * @return bool|\WP_Error True if valid, WP_Error if invalid.
	 */
	public function validate_uploaded_file( array $file, array $allowed_types = [ 'csv' ] ) {
		// Check for upload errors.
		if ( UPLOAD_ERR_OK !== $file['error'] ) {
			return new WP_Error(
				'upload_error',
				$this->get_upload_error_message( $file['error'] )
			);
		}

		// Validate file extension.
		$file_extension = strtolower( pathinfo( $file['name'], PATHINFO_EXTENSION ) );
		if ( ! in_array( $file_extension, $allowed_types, true ) ) {
			return new WP_Error(
				'invalid_file_type',
				sprintf(
					/* translators: %s: comma-separated list of allowed file types */
					esc_html__( 'Please upload a file with one of these extensions: %s', 'rewardswp' ),
					implode( ', ', $allowed_types )
				)
			);
		}

		// Validate file size against PHP limits.
		$max_size = $this->get_max_upload_size();
		if ( $file['size'] > $max_size ) {
			return new WP_Error(
				'file_too_large',
				sprintf(
					/* translators: %d: maximum file size in MB */
					esc_html__( 'File size must be less than %d MB.', 'rewardswp' ),
					$this->get_max_upload_size_mb()
				)
			);
		}

		// Check if file exists and is readable.
		$tmp_name = $file['tmp_name'] ?? '';
		if ( empty( $tmp_name ) || ! is_uploaded_file( $tmp_name ) ) {
			return new WP_Error(
				'file_not_readable',
				esc_html__( 'Could not read the uploaded file.', 'rewardswp' )
			);
		}

		$tmp_info = new \SplFileInfo( $tmp_name );
		if ( ! $tmp_info->isFile() || ! $tmp_info->isReadable() ) {
			return new WP_Error(
				'file_not_readable',
				esc_html__( 'Could not read the uploaded file.', 'rewardswp' )
			);
		}

		return true;
	}

	/**
	 * Get human-readable upload error message.
	 *
	 * @since 1.0.0
	 *
	 * @param int $error_code PHP upload error code.
	 *
	 * @return string Error message.
	 */
	private function get_upload_error_message( int $error_code ): string {
		switch ( $error_code ) {
			case UPLOAD_ERR_INI_SIZE:
				return sprintf(
					/* translators: %d: maximum file size in MB */
					esc_html__( 'File is too large. Maximum size allowed: %d MB.', 'rewardswp' ),
					$this->get_max_upload_size_mb()
				);
			case UPLOAD_ERR_FORM_SIZE:
				return esc_html__( 'File exceeded the maximum size specified in the form.', 'rewardswp' );
			case UPLOAD_ERR_PARTIAL:
				return esc_html__( 'File was only partially uploaded.', 'rewardswp' );
			case UPLOAD_ERR_NO_FILE:
				return esc_html__( 'No file was uploaded.', 'rewardswp' );
			case UPLOAD_ERR_NO_TMP_DIR:
				return esc_html__( 'Missing temporary folder.', 'rewardswp' );
			case UPLOAD_ERR_CANT_WRITE:
				return esc_html__( 'Failed to write file to disk.', 'rewardswp' );
			case UPLOAD_ERR_EXTENSION:
				return esc_html__( 'File upload stopped by extension.', 'rewardswp' );
			default:
				return esc_html__( 'Unknown upload error.', 'rewardswp' );
		}
	}

	/**
	 * Parse CSV file and return structured data.
	 *
	 * @since 1.0.0
	 *
	 * @param string $file_path      Path to CSV file.
	 * @param array  $required_headers Required headers for validation.
	 * @param int    $preview_limit  Number of rows for preview (default: 10).
	 *
	 * @return array|\WP_Error Parsed data or error.
	 */
	public function parse_csv_file( string $file_path, array $required_headers = [], int $preview_limit = 10 ) {
		try {
			$file = new \SplFileObject( $file_path, 'r' );
		} catch ( \RuntimeException $e ) {
			return new WP_Error( 'file_open_failed', esc_html__( 'Could not open the CSV file.', 'rewardswp' ) );
		}

		// Read headers.
		$file->setFlags( \SplFileObject::READ_CSV );
		$headers = $file->fgetcsv();
		if ( false === $headers || ( is_array( $headers ) && 1 === count( $headers ) && empty( $headers[0] ) ) ) {
			return new WP_Error( 'invalid_csv', esc_html__( 'Invalid CSV file format.', 'rewardswp' ) );
		}

		// Validate headers if required.
		if ( ! empty( $required_headers ) ) {
			$missing_headers = array_diff( $required_headers, $headers );
			if ( ! empty( $missing_headers ) ) {
				return new WP_Error(
					'invalid_headers',
					sprintf(
						/* translators: %s: comma-separated list of missing headers */
						esc_html__( 'CSV file is missing required headers: %s', 'rewardswp' ),
						implode( ', ', $missing_headers )
					)
				);
			}
		}

		// Read preview rows.
		$preview_rows = [];
		$row_count    = 0;

		while ( ! $file->eof() && $row_count < $preview_limit ) {
			$row = $file->fgetcsv();
			if ( false !== $row && ! empty( array_filter( $row ) ) ) { // Skip empty rows.
				$preview_rows[] = array_combine( $headers, $row );
				++$row_count;
			}
		}

		// Count total rows.
		$total_rows = $row_count;
		while ( ! $file->eof() ) {
			$row = $file->fgetcsv();
			if ( false !== $row && ! empty( array_filter( $row ) ) ) {
				++$total_rows;
			}
		}

		return [
			'headers'       => $headers,
			'preview'       => $preview_rows,
			'total_rows'    => $total_rows,
			'preview_count' => count( $preview_rows ),
			'file_path'     => $file_path,
		];
	}

	/**
	 * Process CSV file in batches for import.
	 *
	 * This method reads the CSV file and yields batches of data
	 * for processing without loading the entire file into memory.
	 *
	 * @since 1.0.0
	 *
	 * @param string $file_path  Path to CSV file.
	 * @param int    $batch_size Number of rows per batch.
	 * @param int    $offset     Starting row offset (0-based, excludes header).
	 *
	 * @return \Generator|\WP_Error Generator yielding batches or error.
	 */
	public function process_csv_in_batches( string $file_path, int $batch_size = 50, int $offset = 0 ) {
		try {
			$file = new \SplFileObject( $file_path, 'r' );
		} catch ( \RuntimeException $e ) {
			return new WP_Error( 'file_open_failed', esc_html__( 'Could not open the CSV file.', 'rewardswp' ) );
		}

		// Read and store headers.
		$file->setFlags( \SplFileObject::READ_CSV );
		$headers = $file->fgetcsv();
		if ( false === $headers || ( is_array( $headers ) && 1 === count( $headers ) && empty( $headers[0] ) ) ) {
			return new WP_Error( 'invalid_csv', esc_html__( 'Invalid CSV file format.', 'rewardswp' ) );
		}

		// Skip to offset if provided.
		$current_row = 0;
		while ( $current_row < $offset && ! $file->eof() ) {
			$row = $file->fgetcsv();
			if ( false !== $row && ! empty( array_filter( $row ) ) ) {
				++$current_row;
			}
		}

		// Process in batches.
		$batch       = [];
		$batch_count = 0;

		while ( ! $file->eof() ) {
			$row = $file->fgetcsv();
			if ( false !== $row && ! empty( array_filter( $row ) ) ) { // Skip empty rows.
				$batch[] = array_combine( $headers, $row );
				++$batch_count;

				if ( $batch_count >= $batch_size ) {
					yield $batch;
					$batch       = [];
					$batch_count = 0;
				}
			}
		}

		// Yield remaining rows.
		if ( ! empty( $batch ) ) {
			yield $batch;
		}
	}

	/**
	 * Get supported file formats.
	 *
	 * @since 1.0.0
	 *
	 * @return array Supported formats with their extensions.
	 */
	public function get_supported_formats(): array {
		return [
			'csv' => [
				'extension' => 'csv',
				'mime_type' => 'text/csv',
				'label'     => __( 'CSV (Comma Separated Values)', 'rewardswp' ),
			],
		];
	}

	/**
	 * Get import statistics from parsed data.
	 *
	 * @since 1.0.0
	 *
	 * @param array $parsed_data Data from parse_csv_file().
	 *
	 * @return array Statistics array.
	 */
	public function get_import_statistics( array $parsed_data ): array {
		if ( empty( $parsed_data['preview'] ) ) {
			return [
				'total_rows'     => 0,
				'preview_count'  => 0,
				'estimated_time' => 0,
				'memory_usage'   => 0,
			];
		}

		$total_rows               = $parsed_data['total_rows'] ?? 0;
		$estimated_time_per_row   = 0.1; // seconds
		$estimated_memory_per_row = 1024; // bytes

		return [
			'total_rows'     => $total_rows,
			'preview_count'  => $parsed_data['preview_count'] ?? 0,
			'estimated_time' => $total_rows * $estimated_time_per_row,
			'memory_usage'   => $total_rows * $estimated_memory_per_row,
			'file_size'      => file_exists( $parsed_data['file_path'] ?? '' ) ? filesize( $parsed_data['file_path'] ) : 0,
		];
	}
}
