<?php
/**
 * @author 1902 Software
 * @copyright Copyright © 2023 1902 Software (https://1902software.com/magento/)
 * @package WriteTextAI_WriteTextAI
 */

declare(strict_types=1);

namespace WriteTextAI\WriteTextAI\Model;

use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Model\ResourceModel\Product\Action as ProductAction;
use WriteTextAI\WriteTextAI\Helper\Data;
use WriteTextAI\WriteTextAI\Helper\Product as ProductHelper;
use WriteTextAI\WriteTextAI\Model\GeneratedText;
use WriteTextAI\WriteTextAI\Model\GeneratedTextOptimized;
use WriteTextAI\WriteTextAI\Model\AiProductManager;
use WriteTextAI\WriteTextAI\Model\ApiManager;
use WriteTextAI\WriteTextAI\Model\OptionSource\Filter\Fields;
use WriteTextAI\WriteTextAI\Model\ReviewStatus;
use WriteTextAI\WriteTextAI\Helper\Store as StoreHelper;
use WriteTextAI\WriteTextAI\Helper\Html;
use WriteTextAI\WriteTextAI\Model\BulkTransferRequests;
use WriteTextAI\WriteTextAI\Model\BulkTransferRequestsFactory;
use WriteTextAI\WriteTextAI\Model\BulkTransferRequestsManager;
use WriteTextAI\WriteTextAI\Logger\CronLogger;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Stdlib\DateTime\DateTime;

class BulkTransferCronManager
{
    /**
     * Maximum retries for failed API calls
     */
    const MAX_RETRIES = 3;
    
    /**
     * Memory limit in MB before forcing cleanup
     */
    const MEMORY_LIMIT = 256;
    
    /**
     * Delay between operations in microseconds (0.1 seconds)
     */
    const OPERATION_DELAY = 100000;

    /**
     * @var GeneratedText
     */
    protected $generatedText;

    /**
     * @var GeneratedTextOptimized
     */
    protected $generatedTextOptimized;

    /**
     * @var ProductAction
     */
    protected $productAction;

    /**
     * @var Data
     */
    protected $helper;

    /**
     * @var AiProductManager
     */
    protected $aiProductManager;

    /**
     * @var ProductHelper
     */
    protected $productHelper;

    /**
     * @var ProductRepositoryInterface
     */
    protected $productRepository;

    /**
     * @var ApiManager
     */
    protected $apiManager;

    /**
     * @var ReviewStatus
     */
    protected $reviewStatus;

    /**
     * @var StoreHelper
     */
    protected $storeHelper;

    /**
     * @var Html
     */
    protected $htmlHelper;

    /**
     * @var BulkTransferRequestsFactory
     */
    protected $bulkTransferRequestsFactory;

    /**
     * @var BulkTransferRequestsManager
     */
    protected $bulkTransferRequestsManager;

    /**
     * @var CronLogger
     */
    protected $cronLogger;

    /**
     * @var DateTime
     */
    protected $dateTime;

    /**
     * @var bool
     */
    protected $isAltIncluded = false;

    /**
     * @var array
     */
    protected $transferResults = [];

    /**
     * @var string
     */
    protected $startedAt = '';

    /**
     * @var string
     */
    protected $actorEmail = '';

    /**
     * Constructor
     *
     * @param GeneratedText $generatedText
     * @param GeneratedTextOptimized $generatedTextOptimized
     * @param ProductAction $productAction
     * @param Data $helper
     * @param AiProductManager $aiProductManager
     * @param ProductHelper $productHelper
     * @param ProductRepositoryInterface $productRepository
     * @param ApiManager $apiManager
     * @param ReviewStatus $reviewStatus
     * @param StoreHelper $storeHelper
     * @param Html $htmlHelper
     * @param BulkTransferRequestsFactory $bulkTransferRequestsFactory
     * @param CronLogger $cronLogger
     * @param DateTime $dateTime
     */
    public function __construct(
        GeneratedText $generatedText,
        GeneratedTextOptimized $generatedTextOptimized,
        ProductAction $productAction,
        Data $helper,
        AiProductManager $aiProductManager,
        ProductHelper $productHelper,
        ProductRepositoryInterface $productRepository,
        ApiManager $apiManager,
        ReviewStatus $reviewStatus,
        StoreHelper $storeHelper,
        Html $htmlHelper,
        BulkTransferRequestsFactory $bulkTransferRequestsFactory,
        BulkTransferRequestsManager $bulkTransferRequestsManager,
        CronLogger $cronLogger,
        DateTime $dateTime
    ) {
        $this->generatedText = $generatedText;
        $this->generatedTextOptimized = $generatedTextOptimized;
        $this->productAction = $productAction;
        $this->helper = $helper;
        $this->aiProductManager = $aiProductManager;
        $this->productHelper = $productHelper;
        $this->productRepository = $productRepository;
        $this->apiManager = $apiManager;
        $this->reviewStatus = $reviewStatus;
        $this->storeHelper = $storeHelper;
        $this->htmlHelper = $htmlHelper;
        $this->bulkTransferRequestsFactory = $bulkTransferRequestsFactory;
        $this->bulkTransferRequestsManager = $bulkTransferRequestsManager;
        $this->cronLogger = $cronLogger;
        $this->dateTime = $dateTime;
    }

    /**
     * Process bulk transfer request
     *
     * @param int $bulkRequestId
     * @return bool
     */
    public function processBulkTransfer(int $bulkRequestId): bool
    {
        $this->cronLogger->logRequest("Starting bulk transfer process for request ID: {$bulkRequestId}");
        $this->cronLogger->logRequest("Initial memory usage: " . memory_get_usage(true) / 1024 / 1024 . " MB");
        
        try {
            // Load bulk transfer request
            $bulkRequest = $this->loadBulkTransferRequest($bulkRequestId);
            if (!$bulkRequest) {
                $this->cronLogger->logRequest("Bulk transfer request not found: {$bulkRequestId}");
                return false;
            }

            // Parse queue IDs
            $queueIds = $this->parseIds($bulkRequest->getQueueIds());
            $completedIds = $this->parseIds($bulkRequest->getCompletedIds());
            $this->actorEmail = $bulkRequest->getUser();
            $this->startedAt = $bulkRequest->getStartedAt();
            
            if (empty($queueIds)) {
                $this->cronLogger->logRequest("No items to process in queue for request: {$bulkRequestId}");
                return true;
            }

            // Parse configuration data
            $stores = explode(',', $bulkRequest->getStoresSelected());
            $fields = explode(',', $bulkRequest->getFieldsSelected());
            $storeFilter = (int)$bulkRequest->getStoreFilter();
            
            $this->cronLogger->logRequest("Processing " . count($queueIds) . " items for stores: " . implode(', ', $stores));
            
            $language = $this->storeHelper->getFormattedLanguage($storeFilter);
            $fieldConfig = $this->getFieldConfig();
            
            $processedCount = 0;
            $failedCount = 0;
            $loadedProducts = []; // Cache for loaded products
            
            // Send "Running" notification at start
            $this->sendBulkTransferNotification(
                $bulkRequest,
                'Running',
                $queueIds,
                [],
                $completedIds,
                [],
                $processedCount,
                $failedCount,
                $storeFilter,
                $language
            );
            
            // Process each product in the queue
            foreach ($queueIds as $index => $productId) {
                try {
                    $productId = (int)$productId;
                    
                    // Skip if already completed
                    if (in_array($productId, $completedIds)) {
                        continue;
                    }
                    
                    // Special monitoring for the problematic 10th product
                    $currentMemory = memory_get_usage(true) / 1024 / 1024;
                    $productIndex = $index + 1;
                    
                    $this->cronLogger->logRequest("Processing product #{$productIndex} ID: {$productId}, Memory: {$currentMemory}MB, Cache size: " . count($loadedProducts));
                    
                    // Light cleanup before processing high-index products, but don't clear cache
                    if ($productIndex % 15 == 0) {
                        $this->cronLogger->logRequest("Light pre-processing cleanup for product #{$productIndex} (ID: {$productId})");
                        // Only perform light cleanup, don't clear product cache
                        gc_collect_cycles();
                        \WriteTextAI\WriteTextAI\Model\Api\CurlBuilder::cleanupPool();
                        $currentMemory = memory_get_usage(true) / 1024 / 1024;
                        $this->cronLogger->logRequest("After light cleanup - Memory: {$currentMemory}MB, Cache size: " . count($loadedProducts));
                    }
                    
                    // Process the transfer for this product
                    $success = $this->processProductTransfer(
                        $productId,
                        $fields,
                        $stores,
                        $storeFilter,
                        $language,
                        $fieldConfig,
                        $loadedProducts
                    );
                    
                    if ($success) {
                        // Update progress: move from queue to completed
                        $this->updateProgress($bulkRequest, $productId);
                        $processedCount++;
                        $this->cronLogger->logRequest("Successfully processed product ID: {$productId}");
                        
                        // Send progress notification every 1 successful item
                        if ($processedCount % 1 == 0) {
                            $currentQueueIds = $this->parseIds($bulkRequest->getQueueIds());
                            $currentCompletedIds = $this->parseIds($bulkRequest->getCompletedIds());
                            
                            $this->sendBulkTransferNotification(
                                $bulkRequest,
                                'Running',
                                $currentQueueIds,
                                [],
                                $currentCompletedIds,
                                [],
                                $processedCount,
                                $failedCount,
                                $storeFilter,
                                $language
                            );
                        }
                    } else {
                        $failedCount++;
                        $this->cronLogger->logRequest("Failed to process product ID: {$productId}");
                    }
                    
                    // Add delay between operations to prevent resource exhaustion
                    usleep(self::OPERATION_DELAY);
                    
                    // Periodic cleanup - less aggressive approach
                    // if (($processedCount + $failedCount) % 10 == 0) {
                    //     $this->cronLogger->logRequest("Performing periodic cleanup after processing " . ($processedCount + $failedCount) . " products");
                    //     $this->performCleanup();
                    //     // Only clear cache if it's getting very large
                    //     $cacheSize = count($loadedProducts);
                    //     if ($cacheSize > 15) {
                    //         $this->cronLogger->logRequest("Product cache size: {$cacheSize}, clearing excess cached products");
                    //         // Keep only the last 10 products in cache
                    //         $loadedProducts = array_slice($loadedProducts, -10, null, true);
                    //     }
                    // }
                    
                } catch (\Exception $e) {
                    $this->cronLogger->logRequest("Error processing product {$productId}: " . $e->getMessage());
                    $this->addFailedResult($productId, $stores);
                    $failedCount++;
                    continue;
                }
            }
            
            // Determine final status based on results
            $finalStatus = 'Completed';
            if ($processedCount === 0 && $failedCount > 0) {
                $finalStatus = 'Failed';
            } elseif ($processedCount > 0) {
                $finalStatus = 'Completed';
            }
            
            // Send final notification
            $this->sendBulkTransferNotification(
                $bulkRequest,
                $finalStatus,
                [],
                [],
                $this->parseIds($bulkRequest->getCompletedIds()), // Get updated completed IDs
                [], // We don't track failed IDs in our current implementation
                $processedCount,
                $failedCount,
                $storeFilter,
                $language
            );
                
            // Clean up progress from database
            $this->bulkTransferRequestsManager->delete($bulkRequest->getUser());

            // Send email with transfer results
            $this->sendTransferResultsEmail();
            
            
            $this->cronLogger->logRequest("Bulk transfer completed. Processed: {$processedCount}, Failed: {$failedCount}");
            return true;
            
        } catch (\Exception $e) {
            $this->cronLogger->logRequest("Error in bulk transfer process: " . $e->getMessage());
            
            // Send "Failed" notification on exception
            if (isset($bulkRequest)) {
                try {
                    $this->sendBulkTransferNotification(
                        $bulkRequest,
                        'Failed',
                        isset($queueIds) ? $queueIds : [],
                        [],
                        isset($completedIds) ? $completedIds : [],
                        [],
                        isset($processedCount) ? $processedCount : 0,
                        isset($failedCount) ? $failedCount : 0,
                        isset($storeFilter) ? $storeFilter : 0,
                        isset($language) ? $language : 'en'
                    );
                } catch (\Exception $notificationException) {
                    $this->cronLogger->logRequest("Failed to send error notification: " . $notificationException->getMessage());
                }
            }
            
            // Send email with transfer results (including failed products)
            $this->sendTransferResultsEmail();
            
            return false;
        } finally {
            // Final cleanup
            $this->forceCloseFileHandles();
            \WriteTextAI\WriteTextAI\Model\Api\CurlBuilder::cleanupPool();
            gc_collect_cycles();
        }
    }

    /**
     * Load bulk transfer request by ID
     *
     * @param int $bulkRequestId
     * @return BulkTransferRequests|null
     */
    private function loadBulkTransferRequest(int $bulkRequestId): ?BulkTransferRequests
    {
        try {
            $bulkRequest = $this->bulkTransferRequestsFactory->create();
            $bulkRequest->load($bulkRequestId);
            
            if (!$bulkRequest->getId()) {
                return null;
            }
            
            return $bulkRequest;
        } catch (\Exception $e) {
            $this->cronLogger->logRequest("Error loading bulk request: " . $e->getMessage());
            return null;
        }
    }

    /**
     * Parse comma-separated IDs string to array
     *
     * @param string|null $idsString
     * @return array
     */
    private function parseIds(?string $idsString): array
    {
        if (empty($idsString)) {
            return [];
        }
        
        return array_filter(array_map('trim', explode(',', $idsString)));
    }

    /**
     * Process transfer for a single product (reuses logic from Transfer.php)
     *
     * @param int $productId
     * @param array $fields
     * @param array $stores
     * @param int $storeFilter
     * @param string $language
     * @param array $fieldConfig
     * @param array &$loadedProducts
     * @return bool
     */
    private function processProductTransfer(
        int $productId,
        array $fields,
        array $stores,
        int $storeFilter,
        string $language,
        array $fieldConfig,
        array &$loadedProducts
    ): bool {
        try {
            $this->cronLogger->logRequest("Starting processProductTransfer for product ID: {$productId}");
            
            // Get generated text for the product
            $item = $this->generatedText->getTextFields($productId, $storeFilter, true);
            $productFields = $this->removeEmptyAndTransferredFields($item, $fields);
            $this->cronLogger->logRequest("Retrieved text fields for product: {$productId}, fields: " . implode(',', $productFields));

            $images = [];
            $altTextProcessed = false;
            
            // Load product with caching
            if (!isset($loadedProducts[$productId])) {
                try {
                    $this->cronLogger->logRequest("Loading product from repository: {$productId}");
                    $loadedProducts[$productId] = $this->productRepository->getById($productId, true, $storeFilter);
                    $this->cronLogger->logRequest("Successfully loaded product: {$productId}");
                } catch (NoSuchEntityException $e) {
                    $this->cronLogger->logRequest("Product not found: {$productId} - " . $e->getMessage());
                    $this->addFailedResult($productId, $stores);
                    return false;
                } catch (\Exception $e) {
                    $this->cronLogger->logRequest("Error loading product {$productId}: " . $e->getMessage());
                    $this->addFailedResult($productId, $stores);
                    return false;
                }
            }
            
            $productSelected = $loadedProducts[$productId];
            
            try {
                $this->cronLogger->logRequest("Getting published images for product: {$productId}");
                $this->cronLogger->logRequest("Product SKU: " . $productSelected->getSku() . ", Store: {$storeFilter}");
                
                // Check if product has media gallery before processing
                $mediaGalleryImages = $productSelected->getMediaGalleryImages();
                $mediaCount = $mediaGalleryImages ? $mediaGalleryImages->getSize() : 0;
                $this->cronLogger->logRequest("Product {$productId} has {$mediaCount} media gallery images");
                
                $images = $this->productHelper->getPublishedImages($productSelected, $storeFilter, $language);
                $this->cronLogger->logRequest("Retrieved " . count($images) . " published images for product: {$productId}");
            } catch (\Magento\Framework\Exception\FileSystemException $e) {
                $this->cronLogger->logRequest("FileSystem error getting images for product {$productId}: " . $e->getMessage());
                $this->cronLogger->logRequest("FileSystem error file: " . $e->getFile() . ", line: " . $e->getLine());
                $images = [];
            } catch (\InvalidArgumentException $e) {
                $this->cronLogger->logRequest("Invalid argument error getting images for product {$productId}: " . $e->getMessage());
                $this->cronLogger->logRequest("Invalid argument error file: " . $e->getFile() . ", line: " . $e->getLine());
                $images = [];
            } catch (\Exception $e) {
                $this->cronLogger->logRequest("General error getting published images for product {$productId}: " . $e->getMessage());
                $this->cronLogger->logRequest("Error type: " . get_class($e));
                $this->cronLogger->logRequest("Error file: " . $e->getFile() . ", line: " . $e->getLine());
                $this->cronLogger->logRequest("Stack trace: " . $e->getTraceAsString());
                $images = [];
            }
            
            // Handle alt text if selected
            if (in_array("alt_text", $fields)) {
                try {
                    $this->cronLogger->logRequest("Processing alt text for product: {$productId}");
                    $images = $this->productHelper->getImages($productSelected, $storeFilter, $language);
                    $this->isAltIncluded = true;
                    $this->cronLogger->logRequest("Retrieved " . count($images) . " images for alt text processing: {$productId}");

                    if (!empty($images)) {
                        $this->cronLogger->logRequest("Starting alt text transfer for product: {$productId}");
                        $altTextProcessed = $this->transferAltText($stores, $productId, $images, $language);
                        $this->cronLogger->logRequest("Completed alt text transfer for product: {$productId}, processed: " . ($altTextProcessed ? 'yes' : 'no'));
                    }
                } catch (\Exception $e) {
                    $this->cronLogger->logRequest("Error processing alt text for product {$productId}: " . $e->getMessage());
                    // Continue processing without alt text
                    $altTextProcessed = false;
                }
            }

            // Transfer product fields if any
            if (!empty($productFields)) {
                try {
                    $this->cronLogger->logRequest("Saving generated text for product: {$productId}");
                    $this->generatedTextOptimized->saveText(
                        (string)$storeFilter,
                        (string)$productId,
                        $item['page_title'],
                        $item['page_description'],
                        $item['product_description'],
                        $item['short_product_description'],
                        $item['open_graph'],
                        $productFields
                    );
                    $this->cronLogger->logRequest("Successfully saved text for product: {$productId}");

                    // Transfer to each store
                    foreach ($stores as $storeId) {
                        try {
                            $this->cronLogger->logRequest("Transferring to store {$storeId} for product: {$productId}");
                            $this->transfer(
                                $productId,
                                (int)$storeId,
                                $productFields,
                                $images,
                                $item['page_title'],
                                $item['page_description'],
                                $item['product_description'],
                                $item['short_product_description'],
                                $item['open_graph']
                            );
                            $this->cronLogger->logRequest("Successfully transferred to store {$storeId} for product: {$productId}");
                        } catch (\Exception $e) {
                            $this->cronLogger->logRequest("Error transferring to store {$storeId} for product {$productId}: " . $e->getMessage());
                            throw $e; // Re-throw to fail the product processing
                        }
                    }
                } catch (\Exception $e) {
                    $this->cronLogger->logRequest("Error in product field transfer for product {$productId}: " . $e->getMessage());
                    throw $e; // Re-throw to fail the product processing
                }
            }
            
            // Update review status
            try {
                $textTypes = [];
                foreach ($fields as $selectedField) {
                    if (isset($fieldConfig[$selectedField])) {
                        $textTypes[] = $fieldConfig[$selectedField];
                    }
                }
                
                $this->cronLogger->logRequest("Updating review status for product: {$productId}");
                $this->reviewStatus->updateReview($productFields, $productId, $stores);
                $this->cronLogger->logRequest("Successfully updated review status for product: {$productId}");
            } catch (\Exception $e) {
                $this->cronLogger->logRequest("Error updating review status for product {$productId}: " . $e->getMessage());
                // Don't fail the entire process for review status update errors
            }
            
            // Determine if product should be tracked as success or skipped
            if (!empty($productFields) || $altTextProcessed) {
                // Track successful processing (either product fields or alt text was processed)
                $this->addSuccessResult($productId, $stores);
                $this->cronLogger->logRequest("Marked product {$productId} as successful");
            } else {
                // Track as skipped when both product fields are empty AND alt text was not processed
                $this->addSkippedResult($productId, $stores);
                $this->cronLogger->logRequest("Marked product {$productId} as skipped (no content to process)");
            }
            
            $this->cronLogger->logRequest("Completed processProductTransfer for product ID: {$productId}");
            return true;
            
        } catch (\Exception $e) {
            $this->cronLogger->logRequest("Error in processProductTransfer for product {$productId}: " . $e->getMessage());
            $this->cronLogger->logRequest("Exception stack trace: " . $e->getTraceAsString());
            $this->addFailedResult($productId, $stores);
            return false;
        } finally {
            // Clean up product from cache after processing
            if (isset($loadedProducts[$productId])) {
                unset($loadedProducts[$productId]);
                $this->cronLogger->logRequest("Cleaned up cached product: {$productId}");
            }
        }
    }

    /**
     * Remove empty and already transferred fields (from Transfer.php)
     *
     * @param array $item
     * @param array $fields
     * @return array
     */
    private function removeEmptyAndTransferredFields($item, $fields)
    {
        $productFields = [];

        foreach ($fields as $field) {
            if (isset($item[$field]) &&
                $item[$field] != '' &&
                $item[$field] != null
            ) {
                $productFields[] = $field;
            }
        }

        return $productFields;
    }

    /**
     * Transfer data to Magento (from Transfer.php)
     *
     * @param int $productId
     * @param int $storeId
     * @param array $selectedFields
     * @param array $images
     * @param string $pageTitle
     * @param string $pageDescription
     * @param string $productDescription
     * @param string $productShortDescription
     * @param string $openGraph
     * @return void
     */
    private function transfer(
        $productId,
        $storeId,
        $selectedFields,
        $images,
        $pageTitle,
        $pageDescription,
        $productDescription,
        $productShortDescription,
        $openGraph
    ) {
        $mappingSettings = $this->helper->getMappingSettings();

        $fields = [
            'page_title' => $pageTitle,
            'page_description' => $pageDescription,
            'product_description' => $productDescription,
            'short_product_description' => $productShortDescription,
            'open_graph' => $openGraph
        ];
        
        $toUpdate = [];
        foreach ($fields as $field => $value) {
            if (isset($mappingSettings[$field]) && in_array($field, $selectedFields)) {
                if ($images) {
                    $value = $this->htmlHelper->addAltTextToContentViaPreg($value, $images, $this->isAltIncluded);
                }
                $toUpdate[$mappingSettings[$field]] = $value;
            }
        }

        $this->productAction->updateAttributes(
            [$productId],
            $toUpdate,
            $storeId
        );

        $this->aiProductManager->saveDate($productId, $storeId, 'transferred');
        $this->aiProductManager->saveDate($productId, $storeId, 'reviewed');
    }

    /**
     * Transfer alt text (from Transfer.php)
     *
     * @param array $stores
     * @param int $productId
     * @param array $images
     * @param string $language
     * @return bool Whether any alt text was actually processed
     */
    public function transferAltText($stores, $productId, $images, $language)
    {
        $imagesToUpdate = [];
        $hasProcessedAltText = false;
        
        foreach ($stores as $storeId) {
            $product = $this->productRepository->getById($productId, true, $storeId);
            $mediaGalleryEntries = $product->getMediaGalleryEntries();
    
            foreach ($images as $image) {
                foreach ($mediaGalleryEntries as $mediaGalleryEntry) {
                    if ($mediaGalleryEntry->getId() == $image['id']) {
                        $mediaGalleryEntry->setLabel($image['writetext_alt']);
                        break;
                    }
                }

                try {
                    $getParams = [
                        "storeId" => $storeId,
                        "language" => $language,
                        "imageId" => $image['id']
                    ];

                    $altText = $this->apiCallWithRetry(function() use ($getParams) {
                        return $this->apiManager->getImage($getParams);
                    });

                    if ($altText && isset($altText['altText']['id'])) {
                        $imagesToUpdate[] = [
                            "storeId" => $storeId,
                            "language" => $language,
                            "imageId" => $altText['imageId'],
                            "textId" => $altText['altText']['id'],
                            "value" => $image['writetext_alt'],
                            "publish" => true,
                            "reviewed" => true
                        ];
                        $hasProcessedAltText = true;
                    }
                } catch (\WriteTextAI\WriteTextAI\Exception\ApiException $e) {
                    if ($e->getCode() !== 404) {
                        throw $e;
                    }
                } catch (\Exception $e) {
                    $this->cronLogger->logRequest("Error getting image data: " . $e->getMessage());
                }
            }
        
            $product->setMediaGalleryEntries($mediaGalleryEntries);
            $this->productRepository->save($product);

            $this->aiProductManager->saveDate($productId, $storeId, 'transferred');
            $this->aiProductManager->saveDate($productId, $storeId, 'reviewed');
        }
        
        if (!empty($imagesToUpdate)) {
            $this->apiCallWithRetry(function() use ($imagesToUpdate) {
                return $this->apiManager->batchUpdateAltText(json_encode([
                    "images" => $imagesToUpdate
                ]));
            });
        }
        
        return $hasProcessedAltText;
    }

    /**
     * Update progress by moving processed item from queue to completed
     *
     * @param BulkTransferRequests $bulkRequest
     * @param int $productId
     * @return void
     */
    private function updateProgress(BulkTransferRequests $bulkRequest, int $productId): void
    {
        try {
            // Get current queue and completed IDs
            $queueIds = $this->parseIds($bulkRequest->getQueueIds());
            $completedIds = $this->parseIds($bulkRequest->getCompletedIds());
            
            // Remove from queue
            $queueIds = array_diff($queueIds, [$productId]);
            
            // Add to completed
            if (!in_array($productId, $completedIds)) {
                $completedIds[] = $productId;
            }
            
            // Update the record
            $bulkRequest->setQueueIds(implode(',', $queueIds));
            $bulkRequest->setCompletedIds(implode(',', $completedIds));
            $bulkRequest->save();
            
            $this->cronLogger->logRequest("Updated progress: Product {$productId} moved to completed. Remaining in queue: " . count($queueIds));
            
        } catch (\Exception $e) {
            $this->cronLogger->logRequest("Error updating progress: " . $e->getMessage());
        }
    }

    /**
     * Get field configuration mapping
     *
     * @return array
     */
    private function getFieldConfig(): array
    {
        return [
            'page_title' => Fields::PAGE_TITLE,
            'page_description' => Fields::PAGE_DESCRIPTION,
            'product_description' => Fields::PRODUCT_DESCRIPTION,
            'short_product_description' => Fields::EXCERPT,
            'open_graph' => Fields::OPEN_GRAPH,
        ];
    }

    /**
     * API call with retry logic (from FullAutomationCronManager)
     *
     * @param callable $apiCall
     * @param int $maxRetries
     * @return mixed
     */
    private function apiCallWithRetry(callable $apiCall, int $maxRetries = self::MAX_RETRIES)
    {
        $attempt = 0;
        $lastException = null;
        
        while ($attempt < $maxRetries) {
            try {
                $attempt++;
                $result = $apiCall();
                return $result;
                
            } catch (\Exception $e) {
                $lastException = $e;
                $this->cronLogger->logRequest("API call attempt {$attempt} failed: " . $e->getMessage());
                
                if ($attempt < $maxRetries) {
                    // Exponential backoff: wait longer between retries
                    usleep(pow(2, $attempt) * 100000); // 0.2s, 0.4s, 0.8s delays
                }
            }
        }
        
        if ($lastException) {
            $this->cronLogger->logRequest("API call failed after {$maxRetries} attempts: " . $lastException->getMessage());
        }
        
        return null;
    }

    /**
     * Perform memory and resource cleanup (from FullAutomationCronManager)
     *
     * @return void
     */
    private function performCleanup(): void
    {
        try {
            $memoryBefore = memory_get_usage(true) / 1024 / 1024;
            $this->cronLogger->logRequest("Starting cleanup - Memory before: {$memoryBefore} MB");
            
            // Don't clear opcode cache as it can interfere with image processing
            // if (function_exists('opcache_reset')) {
            //     @opcache_reset();
            // }
            
            // Force garbage collection multiple times
            for ($i = 0; $i < 3; $i++) {
                gc_collect_cycles();
            }
            
            // Clean up cURL connections
            \WriteTextAI\WriteTextAI\Model\Api\CurlBuilder::cleanupPool();
            
            // Force close file handles
            $this->forceCloseFileHandles();
            
            // Skip registry cleanup as it can interfere with Magento operations
            // Registry cleanup disabled to prevent interference with image processing
            
            // Force PHP to release memory
            if (function_exists('gc_mem_caches')) {
                gc_mem_caches();
            }
            
            // Log memory usage after cleanup
            $memoryAfter = memory_get_usage(true) / 1024 / 1024;
            $memoryFreed = $memoryBefore - $memoryAfter;
            $this->cronLogger->logRequest("Memory usage after cleanup: {$memoryAfter} MB (freed: {$memoryFreed} MB)");
            
            // Force additional cleanup if memory limit approached
            if ($memoryAfter > self::MEMORY_LIMIT) {
                $this->cronLogger->logRequest("Memory limit ({" . self::MEMORY_LIMIT . "}MB) approached, forcing aggressive cleanup");
                
                // More aggressive cleanup
                for ($i = 0; $i < 5; $i++) {
                    gc_collect_cycles();
                }
                
                \WriteTextAI\WriteTextAI\Model\Api\CurlBuilder::cleanupPool();
                $this->forceCloseFileHandles();
                
                $memoryAfterAggressive = memory_get_usage(true) / 1024 / 1024;
                $this->cronLogger->logRequest("Memory after aggressive cleanup: {$memoryAfterAggressive} MB");
                
                // If still over limit, log a warning
                if ($memoryAfterAggressive > self::MEMORY_LIMIT) {
                    $this->cronLogger->logRequest("WARNING: Memory usage still high after cleanup: {$memoryAfterAggressive} MB");
                }
            }
            
        } catch (\Exception $e) {
            $this->cronLogger->logRequest("Error during cleanup: " . $e->getMessage());
        }
    }

    /**
     * Force close file handles (from FullAutomationCronManager)
     *
     * @return void
     */
    private function forceCloseFileHandles(): void
    {
        try {
            // Clear file stat cache
            clearstatcache();
            
            // Force close any open streams
            $openResources = get_resources();
            foreach ($openResources as $resource) {
                if (is_resource($resource) && get_resource_type($resource) === 'stream') {
                    @fclose($resource);
                }
            }
            
        } catch (\Exception $e) {
            // Ignore errors in cleanup
        }
    }

    /**
     * Send bulk transfer notification via API
     *
     * @param BulkTransferRequests $bulkRequest
     * @param string $status
     * @param array $queuedIds
     * @param array $runningIds
     * @param array $completedIds
     * @param array $failedIds
     * @param int $completed
     * @param int $failed
     * @param int $storeFilter
     * @param string $language
     * @return void
     */
    private function sendBulkTransferNotification(
        BulkTransferRequests $bulkRequest,
        string $status,
        array $queuedIds,
        array $runningIds,
        array $completedIds,
        array $failedIds,
        int $completed,
        int $failed,
        int $storeFilter,
        string $language
    ): void {
        try {
            $total = $completed + $failed + count($queuedIds);
            
            $notificationData = [
                'type' => "8",
                'subType' => 'transfer',
                'id' => $bulkRequest->getId(),
                'userName' => $bulkRequest->getUser(),
                'recordType' => 'Product',
                'storeId' => (string) $storeFilter,
                'status' => $status,
                'startTime' => $bulkRequest->getData('created_at') ?: $this->dateTime->gmtDate(),
                'endTime' => $this->dateTime->gmtDate(),
                'queuedIds' => $queuedIds,
                'runningIds' => $runningIds,
                'completedIds' => $completedIds,
                'failedIds' => $failedIds,
                'completed' => $completed,
                'failed' => $failed,
                'total' => $total,
                'language' => $language,
                'metaData' => []
            ];
            
            $this->cronLogger->logRequest('Sending bulk transfer notification: ' . json_encode($notificationData));
            
            $this->apiCallWithRetry(function() use ($notificationData) {
                return $this->apiManager->sendCustomNotification($notificationData);
            }, 1); // Only retry once for notifications
            
        } catch (\Exception $e) {
            $this->cronLogger->logRequest('Failed to send bulk transfer notification: ' . $e->getMessage());
            // Don't throw exception - notification failure shouldn't stop the process
        }
    }
    
    /**
     * Initialize transfer results tracking
     *
     * @return void
     */
    private function initializeTransferResults(): void
    {
        if (empty($this->transferResults)) {
            if ($this->startedAt) {
                $date = new \DateTime($this->startedAt, new \DateTimeZone('UTC'));
                $this->startedAt = $date->format('Y-m-d\TH:i:s');
            }
            $this->transferResults = [
                'actorEmail' => $this->actorEmail,
                'startedDate' => $this->startedAt,
                'types' => [
                    [
                        'type' => 'Product',
                        'all' => [
                            'success' => [],
                            'failed' => [],
                            'skipped' => []
                        ],
                        'storeIds' => []
                    ]
                ]
            ];
        }
    }
    
    /**
     * Add a successful result to tracking
     *
     * @param int $productId
     * @param array $stores
     * @return void
     */
    private function addSuccessResult(int $productId, array $stores = []): void
    {
        $this->initializeTransferResults();
        
        // Add to all success
        if (!in_array((string)$productId, $this->transferResults['types'][0]['all']['success'])) {
            $this->transferResults['types'][0]['all']['success'][] = (string)$productId;
        }
        
        // Add to store-specific success
        foreach ($stores as $storeId) {
            if (!isset($this->transferResults['types'][0]['storeIds'][$storeId])) {
                $this->transferResults['types'][0]['storeIds'][$storeId] = [
                    'success' => [],
                    'failed' => [],
                    'skipped' => []
                ];
            }
            
            if (!in_array((string)$productId, $this->transferResults['types'][0]['storeIds'][$storeId]['success'])) {
                $this->transferResults['types'][0]['storeIds'][$storeId]['success'][] = (string)$productId;
            }
        }
    }
    
    /**
     * Add a failed result to tracking
     *
     * @param int $productId
     * @param array $stores
     * @return void
     */
    private function addFailedResult(int $productId, array $stores = []): void
    {
        $this->initializeTransferResults();
        
        // Add to all failed
        if (!in_array((string)$productId, $this->transferResults['types'][0]['all']['failed'])) {
            $this->transferResults['types'][0]['all']['failed'][] = (string)$productId;
        }
        
        // Add to store-specific failed
        foreach ($stores as $storeId) {
            if (!isset($this->transferResults['types'][0]['storeIds'][$storeId])) {
                $this->transferResults['types'][0]['storeIds'][$storeId] = [
                    'success' => [],
                    'failed' => [],
                    'skipped' => []
                ];
            }
            
            if (!in_array((string)$productId, $this->transferResults['types'][0]['storeIds'][$storeId]['failed'])) {
                $this->transferResults['types'][0]['storeIds'][$storeId]['failed'][] = (string)$productId;
            }
        }
    }
    
    /**
     * Add a skipped result to tracking
     *
     * @param int $productId
     * @param array $stores
     * @return void
     */
    private function addSkippedResult(int $productId, array $stores = []): void
    {
        $this->initializeTransferResults();
        
        // Add to all skipped
        if (!in_array((string)$productId, $this->transferResults['types'][0]['all']['skipped'])) {
            $this->transferResults['types'][0]['all']['skipped'][] = (string)$productId;
        }
        
        // Add to store-specific skipped
        foreach ($stores as $storeId) {
            if (!isset($this->transferResults['types'][0]['storeIds'][$storeId])) {
                $this->transferResults['types'][0]['storeIds'][$storeId] = [
                    'success' => [],
                    'failed' => [],
                    'skipped' => []
                ];
            }
            
            if (!in_array((string)$productId, $this->transferResults['types'][0]['storeIds'][$storeId]['skipped'])) {
                $this->transferResults['types'][0]['storeIds'][$storeId]['skipped'][] = (string)$productId;
            }
        }
    }
    
    /**
     * Send transfer results email
     *
     * @return void
     */
    private function sendTransferResultsEmail(): void
    {
        if (!empty($this->transferResults['types'])) {
            try {
                // Format the results based on the number of stores processed
                foreach ($this->transferResults['types'] as $typeIndex => $type) {
                    $storeCount = count($this->transferResults['types'][$typeIndex]['storeIds']);
                    
                    // If multiple stores were processed, keep only storeIds
                    if ($storeCount > 1) {
                        unset($this->transferResults['types'][$typeIndex]['all']);
                    } else {
                        // If only one store or no stores were processed, keep only all
                        unset($this->transferResults['types'][$typeIndex]['storeIds']);
                    }
                }
                
                // Check if there are any processed records
                $hasProcessedRecords = false;
                foreach ($this->transferResults['types'] as $type) {
                    if (isset($type['all'])) {
                        if (!empty($type['all']['success']) ||
                            !empty($type['all']['failed']) ||
                            !empty($type['all']['skipped'])
                        ) {
                            $hasProcessedRecords = true;
                            break;
                        }
                    } elseif (isset($type['storeIds'])) {
                        foreach ($type['storeIds'] as $storeData) {
                            if (!empty($storeData['success']) || !empty($storeData['failed']) || !empty($storeData['skipped'])) {
                                $hasProcessedRecords = true;
                                break 2;
                            }
                        }
                    }
                }
                
                // Only send to API if there are processed records
                if ($hasProcessedRecords) {
                    $this->cronLogger->logRequest('Sending transfer results email: ' . json_encode($this->transferResults, JSON_PRETTY_PRINT));
                    $resultEmail = $this->apiManager->emailBulkTransferStatus($this->transferResults);
                    $this->cronLogger->logRequest('Transfer results email sent: ' . json_encode($resultEmail, JSON_PRETTY_PRINT));
                }

            } catch (\Exception $e) {
                // Silently handle the exception - email failure shouldn't stop the process
                $this->cronLogger->logRequest('Transfer results email failed: ' . $e->getMessage());
                return;
            }
        }
    }
}