<?php

namespace Limb_Chatbot\Includes\Services;

use Limb_Chatbot\Includes\Data_Objects\File;
use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Exceptions\Exception;

/**
 * File Upload Service
 *
 * Handles batch file upload operations with comprehensive validation,
 * directory management, and file processing for AI provider integration.
 *
 * This service is responsible for:
 * - Validating uploaded files (size, type, security)
 * - Creating unique filenames with hash prefixes
 * - Managing upload directories
 * - Processing batch uploads with error handling
 * - Creating File data objects for database storage
 *
 * Supported file types: text/plain, text/csv, application/pdf, 
 * application/msword, application/vnd.openxmlformats-officedocument.wordprocessingml.document,
 * application/json, text/markdown, text/html
 *
 * @package Limb_Chatbot\Includes\Services
 * @since 1.0.0
 */
class File_Upload_Service {

	/**
	 * Default maximum file size in bytes (10MB).
	 *
	 * @since 1.0.0
	 */
	const DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;

	/**
	 * Default allowed MIME types for file uploads.
	 *
	 * @since 1.0.0
	 */
	const DEFAULT_ALLOWED_TYPES = [
		'application/pdf',
		'application/x-pdf',
	];

	/**
	 * Maximum file size for uploads.
	 *
	 * @var int
	 * @since 1.0.0
	 */
	protected int $max_file_size;

	/**
	 * Allowed MIME types for uploads.
	 *
	 * @var null|array
	 * @since 1.0.0
	 */
	protected ?array $allowed_types;

	/**
	 * Target directory for file uploads.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	protected string $target_directory;

	/**
	 * Constructor.
	 *
	 * @param  string  $target_directory  Target directory for uploads.
	 * @param  int|null  $max_file_size  Maximum file size in bytes.
	 * @param  array|null  $allowed_types  Allowed MIME types.
	 *
	 * @since 1.0.0
	 */
	public function __construct( string $target_directory, ?int $max_file_size = null, ?array $allowed_types = null ) {
		$this->target_directory = trailingslashit( $target_directory );
		$this->max_file_size    = $max_file_size ?? self::DEFAULT_MAX_FILE_SIZE;
		$this->allowed_types    = $allowed_types ?? self::DEFAULT_ALLOWED_TYPES;
	}

	/**
	 * Processes batch file upload with comprehensive error handling.
	 *
	 * This method handles multiple file uploads in a single operation:
	 * 1. Validates the files array is not empty
	 * 2. Creates the target upload directory if it doesn't exist
	 * 3. Processes each file individually with validation
	 * 4. Collects successful uploads and errors
	 * 5. Returns a collection with detailed results
	 *
	 * @param  array  $files  Array of uploaded files from $_FILES superglobal.
	 *                        Each file should have: name, type, tmp_name, error, size
	 *
	 * @return Collection Collection containing upload results with properties:
	 *               - items: Array of successfully uploaded File objects
	 *               - errors: Array of error messages indexed by file key
	 *               - total_files: Total count of files attempted
	 *               - total_errors: Count of files that failed to upload
	 * @throws Exception If directory creation fails or no files provided.
	 * @since 1.0.0
	 */
	public function batch_upload( array $files ): Collection {
		if ( empty( $files ) ) {
			throw new Exception( Error_Codes::VALIDATION_INVALID_VALUE,
				__( 'No files provided for upload.', 'limb-chatbot' ) );
		}

		$errors     = array();
		$target_dir = Helper::get_wp_uploaded_file_dir( $this->target_directory );

		// Create the target directory if it doesn't exist
		if ( ! wp_mkdir_p( $target_dir ) ) {
			throw new Exception(
				Error_Codes::FILE_UNABLE_TO_CREATE_UPLOAD_DIRECTORY,
				__( 'Failed to create upload directory.', 'limb-chatbot' )
			);
		}
		$collection = new Collection();
		foreach ( $files as $file_key => $file ) {
			try {
				$upload_result = $this->process_single_file( $file, $file_key, $target_dir );
				$collection->push_item( $upload_result );
			} catch ( Exception $file_exception ) {
				$errors[ $file_key ] = $file_exception->getMessage();
				Helper::log( $file_exception, __METHOD__ . " - File: {$file_key}" );
			}
		}

		$collection->push_property( 'errors', $errors );
		$collection->push_property( 'total_files', count( $files ) );
		$collection->push_property( 'total_errors', count( $errors ) );

		return $collection;
	}

	/**
	 * Processes a single file upload with validation and storage.
	 *
	 * @param  array  $file  File data from $_FILES superglobal.
	 * @param  string  $file_key  File key for error reporting and identification.
	 * @param  string  $target_dir  Absolute path to the target directory.
	 *
	 * @return File File object with all metadata populated.
	 * @throws Exception If file validation fails, file cannot be moved, or File creation fails.
	 * @since 1.0.0
	 */
	protected function process_single_file( array $file, string $file_key, string $target_dir ): File {
		// Validate the uploaded file
		$this->validate_file( $file );

		// Sanitize filename and create unique name
		$sanitized_name = sanitize_file_name( $file['name'] ?? 'knowledge-file' );
		$filename       = $this->generate_unique_filename( $sanitized_name );
		$target_path    = $target_dir . $filename;

		// Move the uploaded file
		if ( ! Helper::wp_handle_limb_upload( $file, $target_path ) ) {
			throw new Exception(
				Error_Codes::FILE_FAILED_TO_MOVE_UPLOADED_FILE,
				__( 'Failed to move uploaded file.', 'limb-chatbot' )
			);
		}

		// Get file type information
		$file_type = wp_check_filetype( $file['name'] );
		$mime_type = $file_type['type'];

		// Create and return file object
		return File::create( array(
			'file_path'     => $this->target_directory . $filename,
			'file_name'     => $filename,
			'original_name' => $sanitized_name,
			'file_size'     => round( $file['size'] ?? 0 ),
			'mime_type'     => $mime_type,
			'status'        => 'uploaded'
		) );
	}

	/**
	 * Validates an uploaded file for security and compatibility.
	 *
	 * Performs comprehensive validation checks:
	 * 1. Verifies the file was actually uploaded via HTTP POST
	 * 2. Checks file size against maximum allowed size
	 * 3. Validates MIME type against allowed types
	 * 4. Ensures file integrity and security
	 *
	 * @param  array  $file  File data from $_FILES superglobal.
	 *
	 * @throws Exception If file validation fails with specific error codes:
	 *                  - DATASET_FILE_UPLOAD_MISSING_FILE: File not uploaded or missing
	 *                  - VALIDATION_FILE_SIZE_LIMIT: File exceeds size limit
	 *                  - VALIDATION_UNSUPPORTED_MIME_TYPE: File type not allowed
	 * @since 1.0.0
	 */
	protected function validate_file( array $file ): void {
		// Check if file was uploaded
		if ( empty( $file['tmp_name'] ) || ! is_uploaded_file( $file['tmp_name'] ) ) {
			throw new Exception(
				Error_Codes::DATASET_FILE_UPLOAD_MISSING_FILE,
				__( 'Invalid or missing uploaded file.', 'limb-chatbot' )
			);
		}

		// Check file size
		if ( $file['size'] > $this->max_file_size ) {
			throw new Exception(
				Error_Codes::VALIDATION_FILE_SIZE_LIMIT,
				sprintf(
					__( 'File size exceeds the allowed limit of %s bytes.', 'limb-chatbot' ),
					$this->max_file_size
				)
			);
		}

		// Check file type
		$file_type = wp_check_filetype( $file['name'] );
		$mime_type = $file_type['type'];

		if ( ! empty( $this->allowed_types ) && ! in_array( $mime_type, $this->allowed_types, true ) ) {
			throw new Exception(
				Error_Codes::VALIDATION_UNSUPPORTED_MIME_TYPE,
				sprintf(
					__( 'File type "%s" is not allowed.', 'limb-chatbot' ),
					$mime_type
				)
			);
		}
	}

	/**
	 * Generates a unique filename with hash prefix to prevent conflicts.
	 *
	 * Creates a unique filename by prepending an 8-character hash to the
	 * sanitized original filename. This prevents filename conflicts and
	 * provides additional security by obscuring the original filename.
	 *
	 * @param  string  $original_name  Original filename to make unique.
	 *
	 * @return string Unique filename with format: {hash}-{sanitized_name}
	 * @since 1.0.0
	 */
	protected function generate_unique_filename( string $original_name ): string {
		return substr( md5( uniqid( '', true ) ), 0, 8 ) . '-' . $original_name;
	}

	/**
	 * Gets the current maximum file size.
	 *
	 * @return int Maximum file size in bytes.
	 * @since 1.0.0
	 */
	public function get_max_file_size(): int {
		return $this->max_file_size;
	}

	/**
	 * Sets the maximum file size for uploads.
	 *
	 * @param  int  $max_file_size  Maximum file size in bytes.
	 *
	 * @return self
	 * @since 1.0.0
	 */
	public function set_max_file_size( int $max_file_size ): self {
		$this->max_file_size = $max_file_size;

		return $this;
	}

	/**
	 * Gets the current allowed MIME types.
	 *
	 * @return array Array of allowed MIME types.
	 * @since 1.0.0
	 */
	public function get_allowed_types(): array {
		return $this->allowed_types;
	}

	/**
	 * Sets the allowed MIME types for uploads.
	 *
	 * @param  array  $allowed_types  Array of allowed MIME types.
	 *
	 * @return self
	 * @since 1.0.0
	 */
	public function set_allowed_types( array $allowed_types ): self {
		$this->allowed_types = $allowed_types;

		return $this;
	}

	/**
	 * Gets the target directory for uploads.
	 *
	 * @return string Target directory path.
	 * @since 1.0.0
	 */
	public function get_target_directory(): string {
		return $this->target_directory;
	}

	/**
	 * Imports files from WordPress Media Library.
	 *
	 * @param  array  $attachment_ids  Array of WordPress attachment IDs.
	 *
	 * @return Collection Collection containing import results with properties:
	 *               - items: Array of successfully imported File objects
	 *               - errors: Array of error messages indexed by attachment ID
	 *               - total_files: Total count of files attempted
	 *               - total_errors: Count of files that failed to import
	 * @throws Exception If directory creation fails or no attachment IDs provided.
	 * @since 1.0.0
	 */
	public function import_from_media_library( array $attachment_ids ): Collection {
		if ( empty( $attachment_ids ) ) {
			throw new Exception( Error_Codes::VALIDATION_INVALID_VALUE,
				__( 'No attachment IDs provided for import.', 'limb-chatbot' ) );
		}

		$errors     = array();
		$target_dir = Helper::get_wp_uploaded_file_dir( $this->target_directory );

		// Create the target directory if it doesn't exist
		if ( ! wp_mkdir_p( $target_dir ) ) {
			throw new Exception(
				Error_Codes::FILE_UNABLE_TO_CREATE_UPLOAD_DIRECTORY,
				__( 'Failed to create upload directory.', 'limb-chatbot' )
			);
		}

		$collection = new Collection();
		foreach ( $attachment_ids as $attachment_id ) {
			try {
				$import_result = $this->process_media_attachment( $attachment_id, $target_dir );
				$collection->push_item( $import_result );
			} catch ( Exception $file_exception ) {
				$errors[ $attachment_id ] = $file_exception->getMessage();
				Helper::log( $file_exception, __METHOD__ . " - Attachment ID: {$attachment_id}" );
			}
		}

		$collection->push_property( 'errors', $errors );
		$collection->push_property( 'total_files', count( $attachment_ids ) );
		$collection->push_property( 'total_errors', count( $errors ) );

		return $collection;
	}

	/**
	 * Processes a single media library attachment for import.
	 *
	 *
	 * @param  int     $attachment_id  WordPress attachment ID.
	 * @param  string  $target_dir     Absolute path to the target directory.
	 *
	 * @return File File object with all metadata populated.
	 * @throws Exception If file validation fails, file cannot be copied, or File creation fails.
	 * @since 1.0.0
	 */
	protected function process_media_attachment( int $attachment_id, string $target_dir ): File {
		// Get the attachment file path
		$source_path = get_attached_file( $attachment_id );

		if ( ! $source_path || ! file_exists( $source_path ) ) {
			throw new Exception(
				Error_Codes::FILE_NOT_FOUND,
				sprintf( __( 'Attachment file not found for ID: %d', 'limb-chatbot' ), $attachment_id )
			);
		}

		// Get file information
		$file_size     = filesize( $source_path );
		$original_name = wp_basename( $source_path );
		$file_type     = wp_check_filetype( $original_name );
		$mime_type     = $file_type['type'];

		// Validate file size
		if ( $file_size > $this->max_file_size ) {
			throw new Exception(
				Error_Codes::VALIDATION_FILE_SIZE_LIMIT,
				sprintf(
					__( 'File size exceeds the allowed limit of %s bytes.', 'limb-chatbot' ),
					$this->max_file_size
				)
			);
		}

		// Validate file type if allowed_types is set
		if ( ! empty( $this->allowed_types ) && ! in_array( $mime_type, $this->allowed_types, true ) ) {
			throw new Exception(
				Error_Codes::VALIDATION_UNSUPPORTED_MIME_TYPE,
				sprintf(
					__( 'File type "%s" is not allowed.', 'limb-chatbot' ),
					$mime_type
				)
			);
		}

		// Generate unique filename and copy file
		$sanitized_name = sanitize_file_name( $original_name );
		$filename       = $this->generate_unique_filename( $sanitized_name );
		$target_path    = $target_dir . $filename;

		if ( ! copy( $source_path, $target_path ) ) {
			throw new Exception(
				Error_Codes::FILE_FAILED_TO_MOVE_UPLOADED_FILE,
				__( 'Failed to copy media library file.', 'limb-chatbot' )
			);
		}

		// Create and return file object
		return File::create( array(
			'file_path'     => $this->target_directory . $filename,
			'file_name'     => $filename,
			'original_name' => $sanitized_name,
			'file_size'     => round( $file_size ),
			'mime_type'     => $mime_type,
			'status'        => 'uploaded',
			'metadata'      => array(
				'source'        => 'media_library',
				'attachment_id' => $attachment_id,
			),
		) );
	}
}
