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

namespace WriteTextAI\WriteTextAI\Controller\Adminhtml\Grid;

use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Catalog\Model\ResourceModel\Product\Action as ProductAction;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\Controller\ResultFactory;
use Magento\Ui\Component\MassAction\Filter;
use WriteTextAI\WriteTextAI\Helper\Data;
use WriteTextAI\WriteTextAI\Model\GeneratedText;
use WriteTextAI\WriteTextAI\Model\AiProductManager;
use WriteTextAI\WriteTextAI\Model\ApiManager;
use Magento\Catalog\Api\ProductRepositoryInterface;
use WriteTextAI\WriteTextAI\Helper\Product as ProductHelper;
use WriteTextAI\WriteTextAI\Model\ReviewStatus;
use Magento\Store\Model\Store;
use WriteTextAI\WriteTextAI\Helper\Store as StoreHelper;
use WriteTextAI\WriteTextAI\Helper\Html;
use Magento\Framework\Stdlib\DateTime\DateTime;
use WriteTextAI\WriteTextAI\Model\Api\Session as ApiSession;
use WriteTextAI\WriteTextAI\Model\BulkTransferRequestsManager;
use WriteTextAI\WriteTextAI\Service\ImageAltTextTransfer;

class MassTransfer extends Action implements HttpPostActionInterface
{
    public const ADMIN_RESOURCE = 'WriteTextAI_WriteTextAI::transfer';

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

    /**
     * @var Filter
     */
    protected $filter;

    /**
     * @var CollectionFactory
     */
    protected $collectionFactory;

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

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

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

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

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

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

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

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

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

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

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

    /**
     * @var ApiSession
     */
    protected $apiSession;

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

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

    /**
     * @var ImageAltTextTransfer
     */
    protected $imageAltTextTransfer;

    /**
     * Constructor
     *
     * @param Context $context
     * @param GeneratedText $generatedText
     * @param Filter $filter
     * @param CollectionFactory $collectionFactory
     * @param ProductAction $productAction
     * @param Data $helper
     * @param AiProductManager $aiProductManager
     * @param ApiManager $apiManager
     * @param ProductRepositoryInterface $productRepository
     * @param ProductHelper $productHelper
     * @param ReviewStatus $reviewStatus
     * @param StoreHelper $storeHelper
     * @param Html $htmlHelper
     * @param DateTime $dateTime
     * @param ApiSession $apiSession
     * @param BulkTransferRequestsManager $bulkTransferRequestsManager
     * @param ImageAltTextTransfer $imageAltTextTransfer
     */
    public function __construct(
        Context $context,
        GeneratedText $generatedText,
        Filter $filter,
        CollectionFactory $collectionFactory,
        ProductAction $productAction,
        Data $helper,
        AiProductManager $aiProductManager,
        ApiManager $apiManager,
        ProductRepositoryInterface $productRepository,
        ProductHelper $productHelper,
        ReviewStatus $reviewStatus,
        StoreHelper $storeHelper,
        Html $htmlHelper,
        DateTime $dateTime,
        ApiSession $apiSession,
        BulkTransferRequestsManager $bulkTransferRequestsManager,
        ImageAltTextTransfer $imageAltTextTransfer
    ) {
        $this->generatedText = $generatedText;
        $this->filter = $filter;
        $this->collectionFactory = $collectionFactory;
        $this->productAction = $productAction;
        $this->helper = $helper;
        $this->aiProductManager = $aiProductManager;
        $this->apiManager = $apiManager;
        $this->productRepository = $productRepository;
        $this->productHelper = $productHelper;
        $this->reviewStatus = $reviewStatus;
        $this->storeHelper = $storeHelper;
        $this->htmlHelper = $htmlHelper;
        $this->dateTime = $dateTime;
        $this->apiSession = $apiSession;
        $this->bulkTransferRequestsManager = $bulkTransferRequestsManager;
        $this->imageAltTextTransfer = $imageAltTextTransfer;
        parent::__construct($context);
    }

    /**
     * Execute
     *
     * @return \Magento\Framework\Controller\Result\Json
     */
    public function execute()
    {
        $result = $this->resultFactory->create(ResultFactory::TYPE_JSON);
        
        try {
            $fields = $this->getRequest()->getPost('fields', []);
            $stores = $this->getRequest()->getPost('stores', []);
            $filters = $this->getRequest()->getPost('filters', []);
            $idsToTransfer = $this->getRequest()->getPost('selected', []);
            $allSelected = $this->getRequest()->getPost('all_selected', $idsToTransfer);
            $batchSize = (int) $this->getRequest()->getPost('batch_size', 3);

            $isSingleRequestProduct = count($allSelected) === 1;

            $storeFilter = isset($filters['store_id']) ?
                $filters['store_id'] : Store::DEFAULT_STORE_ID;
            
            $language = $this->storeHelper->getFormattedLanguage($storeFilter);
            $user = $this->apiSession->getCurrentUser();
            $userEmail = $user->getEmail();
            
            // Check if this is a continuation of existing progress or new transfer
            $existingProgress = $this->bulkTransferRequestsManager->getByUser($userEmail);
            
            if ($existingProgress && !empty($existingProgress->getQueueIds())) {
                // Continue existing transfer - process next batch from queue
                $queueIds = $existingProgress->getQueueIds();
                $completedIds = $existingProgress->getCompletedIds();
                $allSelected = array_unique(array_merge($queueIds, $completedIds));
                
                // The frontend has already determined the batch to process
                // Use the selected IDs from frontend, but validate against queue
                $validBatchIds = array_intersect($idsToTransfer, $queueIds);
                $idsToTransfer = !empty($validBatchIds) ? $validBatchIds : array_slice($queueIds, 0, $batchSize);
                
                if (empty($idsToTransfer)) {
                    // No more products to process
                    //$this->bulkTransferRequestsManager->delete($userEmail);
                    $result->setData([
                        'success' => true,
                        'message' => 'All products processed',
                        'completed' => true
                    ]);
                    return $result;
                }
            } else {
                // New transfer - send initial notification and save progress
                $this->sendMassTransferNotification(
                    $allSelected,
                    $storeFilter,
                    'Running',
                    $allSelected, // Initially all are queued
                    [],
                    [], // None completed yet
                    $language
                );
                
                $this->saveBulkTransferProgress(
                    $userEmail,
                    $stores,
                    $storeFilter,
                    $fields,
                    $allSelected, // All products start in queue
                    [], // None completed initially
                    [], // None successful initially
                    [], // None errored initially
                    [] // None skipped initially
                );
                
                // For new transfers, use the batch provided by frontend
                // Frontend should have sent the first batch in 'selected'
                $idsToTransfer = !empty($idsToTransfer) ? $idsToTransfer : array_slice($allSelected, 0, $batchSize);
            }
            
            $collection = $this->filter->getCollection($this->collectionFactory
                ->create()
                ->addAttributeToSelect('*'))
                ->setStoreId($storeFilter)
                ->addMediaGalleryData();
            
            // Filter collection to only include products in current batch
            $collection->addFieldToFilter('entity_id', ['in' => $idsToTransfer]);
            
            $resultData = [
                'success' => false,
                'message' => __(
                    'Transfer failed because there are no generated' .
                    ' texts yet for the products selected.' .
                    ' Generate text first before transferring.'
                )
            ];
            
            // Get current progress for batch processing
            $currentProgress = $this->bulkTransferRequestsManager->getByUser($userEmail);
            $allQueueIds = $currentProgress ? $currentProgress->getQueueIds() : $allSelected;
            $allCompletedIds = $currentProgress ? $currentProgress->getCompletedIds() : [];
            $allSuccessIds = $currentProgress ? $currentProgress->getSuccessIds() : [];
            $allErrorIds = $currentProgress ? $currentProgress->getErrorIds() : [];
            $allSkippedIds = $currentProgress ? $currentProgress->getSkippedIds() : [];
            
            $batchProcessedIds = [];
            $batchSuccessIds = [];
            $batchSkippedIds = [];
            $batchErrorIds = [];
            
            foreach ($collection as $product) {
                $productId = $product->getId();
                $language = $this->storeHelper->getFormattedLanguage($storeFilter);
                $images = $this->productHelper->getPublishedImages($product, $storeFilter, $language);
                $item = $this->generatedText->getTextFields($productId, $storeFilter, true);
                $productFields = $this->removeEmptyAndTransferredFields($item, $fields);
            
                $altTextProcessed = false;

                if (in_array('alt_text', $fields)) {
                    $images = $this->productHelper->getImages($product, $storeFilter, $language);
                    $this->isAltIncluded = true;

                    $resultData = [
                        'success' => true
                    ];
                    $altTextProcessed = $this->transferImageAltTexts(
                        $images,
                        $productId,
                        $language,
                        $stores,
                        $storeFilter,
                        $product
                    );
                }

                if (!empty($productFields)) {
                    $resultData = [
                        'success' => true,
                        'fields' => $productFields
                    ];

                    $this->generatedText->saveText(
                        $storeFilter,
                        $productId,
                        $item['page_title'],
                        $item['page_description'],
                        $item['product_description'],
                        $item['short_product_description'],
                        $item['open_graph'],
                        $productFields
                    );
    
                    foreach ($stores as $storeId) {
                        $this->transfer(
                            $productId,
                            (int)$storeId,
                            $productFields,
                            $images,
                            $item['page_title'],
                            $item['page_description'],
                            $item['product_description'],
                            $item['short_product_description'],
                            $item['open_graph']
                        );
                    }
                }
                
                // 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);
                    // Collect for batch accumulation instead of individual DB calls
                    $batchSuccessIds[] = $productId;
                } else {
                    // Track as skipped when both product fields are empty AND alt text was not processed
                    $this->addSkippedResult($productId, $stores);
                    // Collect for batch accumulation instead of individual DB calls
                    $batchSkippedIds[] = $productId;
                }

                $this->reviewStatus->updateReview($productFields, $productId, $stores);
                
                // Track this product as processed in current batch
                $batchProcessedIds[] = $productId;
                
                // Send per-product notification to API - immediately after this single product
                if (!in_array($productId, $allCompletedIds)) {
                    $allCompletedIds[] = $productId;
                }
                $currentCompletedIds = $allCompletedIds;
                //convert to single array
                $currentQueueIds = array_diff($allQueueIds, [$productId]);
                $currentQueueIds = array_values($currentQueueIds);

                $this->sendMassTransferNotification(
                    $allSelected,
                    $storeFilter,
                    'Running',
                    $currentQueueIds,
                    [],
                    $currentCompletedIds,
                    $language
                );

                // Save progress after this single product (error_ids and skipped_ids are accumulated separately)
                $this->saveBulkTransferProgress(
                    $userEmail,
                    $stores,
                    $storeFilter,
                    $fields,
                    $currentQueueIds,
                    $currentCompletedIds,
                    $this->mergeArrayValues($batchSuccessIds, $allSuccessIds),
                    $this->mergeArrayValues($batchErrorIds, $allErrorIds),
                    $this->mergeArrayValues($batchSkippedIds, $allSkippedIds)
                );
                
                // Update the running totals for next iteration
                $allCompletedIds = $currentCompletedIds;
                $allQueueIds = $currentQueueIds;
            }

            // After processing all products in batch, use the updated totals
            // (allCompletedIds and allQueueIds were updated after each product)
            $newCompletedIds = $allCompletedIds;
            $newQueueIds = $allQueueIds;
            
            // Ensure arrays contain unique values and are properly indexed
            $newCompletedIds = array_unique($newCompletedIds);
            $newQueueIds = array_unique(array_values($newQueueIds));
            
            // Batch accumulate results in database (one call instead of per-product calls)
            if (!empty($batchSuccessIds) || !empty($batchSkippedIds)) {
                $this->batchAccumulateResults($userEmail, $batchSuccessIds, [], $batchSkippedIds);
            }
            
            // Check if all products are completed
            if (empty($newQueueIds)) {
                // All products completed - send final completion notification
                $this->sendMassTransferNotification(
                    $allSelected,
                    $storeFilter,
                    'Completed',
                    [],
                    [],
                    $newCompletedIds,
                    $language
                );
                
                // Send email with transfer results
                $this->sendTransferResultsEmail($isSingleRequestProduct);
                
                // Clean up progress from database
                $this->bulkTransferRequestsManager->delete($userEmail);
                
                $resultData = [
                    'success' => true,
                    'message' => 'All products transferred successfully',
                    'completed' => true
                ];
            } else {
                // More products to process - per-product notifications and saves already happened
                // No additional batch-level processing needed
                $resultData = [
                    'success' => true,
                    'message' => 'Batch processed successfully',
                    'completed' => false,
                    'remaining' => count($newQueueIds),
                    'processed' => count($newCompletedIds)
                ];
            }
            
            $result->setData($resultData);
        } catch (\Exception $e) {
            $message = $e->getMessage();
            if (!$message) {
                $message = __(
                    'A system error has occurred. Please try again. If the issue persists,'
                    . ' please contact our support team at support@writetext.ai.'
                );
            }

            // Add failed products to transfer results
            if (isset($idsToTransfer, $stores, $userEmail)) {
                foreach ($idsToTransfer as $productId) {
                    $this->addFailedResult($productId, $stores);
                    $batchErrorIds[] = $productId;
                }
                // Batch accumulate error IDs in database (one call instead of per-product calls)
                if (!empty($batchErrorIds)) {
                    $this->batchAccumulateResults($userEmail, [], $batchErrorIds, []);
                }
            }
            
            // Send email with error results if we have any transfer data
            $this->sendTransferResultsEmail($isSingleRequestProduct);

            // Send "Failed" notification on exception
            if (isset($idsToTransfer, $storeFilter, $language)) {
                try {
                    $this->sendMassTransferNotification(
                        $idsToTransfer,
                        $storeFilter,
                        'Failed',
                        [],
                        [],
                        [],
                        $language
                    );
                // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock.DetectedCatch
                } catch (\Exception $notificationException) {
                    // Ignore notification errors
                }
            }
            
            // Clean up progress from database on exception
            if (isset($userEmail)) {
                try {
                    $this->bulkTransferRequestsManager->delete($userEmail);
                // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock.DetectedCatch
                } catch (\Exception $cleanupException) {
                    // Ignore cleanup errors
                }
            }
            
            $result->setData([
                'success' => false,
                'message' => $message
            ]);
        }

        return $result;
    }

    /**
     * Transfer image alt texts
     *
     * @param array $images
     * @param int $productId
     * @param string $language
     * @param array $selectedStores
     * @param int $storeFilter
     * @param \Magento\Catalog\Model\Product $product
     * @return bool Whether any alt text was actually processed
     */
    private function transferImageAltTexts($images, $productId, $language, $selectedStores, $storeFilter, $product)
    {
        $imagesToUpdate = [];
        $storeImageLabels = [];
        $hasProcessedAltText = false;
        
        foreach ($selectedStores as $storeId) {
            $product = $this->productRepository->getById($productId, true, $storeId);
            $mediaGalleryEntries = $product->getMediaGalleryEntries();
            $storeLanguage = $this->storeHelper->getFormattedLanguage($storeId);
    
            foreach ($images as $image) {
                foreach ($mediaGalleryEntries as $mediaGalleryEntry) {
                    if ($mediaGalleryEntry->getId() == $image['id']) {
                        $mediaGalleryEntry->setLabel($image['writetext_alt']);
                        $storeImageLabels[$storeId][$image['id']] = $image['writetext_alt'];
                        break;
                    }
                }

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

                    $altText = $this->apiManager->getImage($getParams);

                    if (isset($altText['altText']['id'])) {
                        $imagesToUpdate[] = [
                            "storeId" => $storeId,
                            "language" => $storeLanguage,
                            "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;
                    }
                }
            }

            $product->setMediaGalleryEntries($mediaGalleryEntries);

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

        if (!empty($storeImageLabels)) {
            $this->imageAltTextTransfer->updateStoreImageLabels($storeImageLabels);
        }

        if (!empty($imagesToUpdate)) {
            $this->apiManager->batchUpdateAltText(json_encode([
                "images" => $imagesToUpdate
            ]));
        }
        
        return $hasProcessedAltText;
    }

    /**
     * Remove empty and already transferred fields
     *
     * @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
     *
     * @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();
        
        //$productDescription = $this->productHelper->convertNewLinesToBr($productDescription);
        //$productShortDescription = $this->productHelper->convertNewLinesToBr($productShortDescription);

        $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');
    }
    
    /**
     * Send mass transfer notification
     *
     * @param array $productIds
     * @param int $storeFilter
     * @param string $status
     * @param array $queuedIds
     * @param array $runningIds
     * @param array $completedIds
     * @param string $language
     * @return void
     */
    private function sendMassTransferNotification(
        array $productIds,
        int $storeFilter,
        string $status,
        array $queuedIds,
        array $runningIds,
        array $completedIds,
        string $language
    ): void {
        try {
            // Generate a unique transfer ID for mass transfers
            $transferId = 'mass_' . count($productIds) . '_' . time();
            
            $user = $this->apiSession->getCurrentUser();
            $email = $user->getEmail();
            
            $completed = count($completedIds);
            $failed = ($status === 'Failed') ? count($productIds) : 0;
            $total = count($productIds);
            
            $notificationData = [
                'type' => "8",
                'subType' => 'transfer',
                'id' => $transferId,
                'userName' => $email,
                'recordType' => 'Product',
                'storeId' => (string) $storeFilter,
                'status' => $status,
                'startTime' => $this->dateTime->gmtDate(),
                'endTime' => $this->dateTime->gmtDate(),
                'queuedIds' => $queuedIds,
                'runningIds' => $runningIds,
                'completedIds' => $completedIds,
                'failedIds' => ($status === 'Failed') ? $productIds : [],
                'skippedIds' => [],
                'completed' => $completed,
                'failed' => $failed,
                'total' => $total,
                'language' => $language,
                'metaData' => []
            ];

            try {
                $existingProgress = $this->bulkTransferRequestsManager->getByUser($email);
                if ($existingProgress) {
                    $notificationData['skippedIds'] = $existingProgress->getSkippedIds() ?? [];
                }
            } catch (\Exception $e) {
                $notificationData['skippedIds'] = [];
            }
            
            // Send notification with single retry
            
            $this->apiManager->sendCustomNotification($notificationData);
        // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock.DetectedCatch
        } catch (\Exception $e) {
            // Don't throw exception - notification failure shouldn't stop the process
        }
    }
    
    /**
     * Save bulk transfer progress to database
     *
     * @param string $userEmail
     * @param array $stores
     * @param int $storeFilter
     * @param array $fields
     * @param array $queueIds
     * @param array $completedIds
     * @param array $successIds
     * @param array $errorIds
     * @param array $skippedIds
     * @return void
     */
    private function saveBulkTransferProgress(
        string $userEmail,
        array $stores,
        int $storeFilter,
        array $fields,
        array $queueIds,
        array $completedIds,
        array $successIds = [],
        array $errorIds = [],
        array $skippedIds = []
    ): void {
        try {
            $this->bulkTransferRequestsManager->saveBulkRequest(
                $userEmail,
                $stores,
                $storeFilter,
                $fields,
                $queueIds,
                $completedIds,
                $successIds,
                $errorIds,
                $skippedIds
            );
        // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock.DetectedCatch
        } catch (\Exception $e) {
            // Don't throw exception - database failure shouldn't stop the transfer process
        }
    }
    
    /**
     * Save bulk transfer progress to database
     *
     * @param \WriteTextAI\WriteTextAI\Model\BulkTransferRequests $request
     * @param string $userEmail
     * @param array $stores
     * @param int $storeFilter
     * @param array $fields
     * @param array $queueIds
     * @param array $completedIds
     * @return void
     */
    private function saveBulkTransferProgressWithRequest(
        $request,
        string $userEmail,
        array $stores,
        int $storeFilter,
        array $fields,
        array $queueIds,
        array $completedIds
    ): void {
        try {
            $this->bulkTransferRequestsManager->saveBulkRequestWithRequest(
                $request,
                $userEmail,
                $stores,
                $storeFilter,
                $fields,
                $queueIds,
                $completedIds
            );
        // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock.DetectedCatch
        } catch (\Exception $e) {
            // Don't throw exception - database failure shouldn't stop the transfer process
        }
    }
    
    /**
     * Initialize transfer results tracking
     *
     * @return void
     */
    private function initializeTransferResults(): void
    {
        if (empty($this->transferResults)) {
            $this->transferResults = [
                'actorEmail' => '',
                'startedDate' => '',
                '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;
            }
        }
    }
    
    // phpcs:disable Generic.Metrics.NestingLevel.TooHigh
    /**
     * Send transfer results email
     *
     * @param bool $isSingleRequestProduct
     * @return void
     */
    private function sendTransferResultsEmail($isSingleRequestProduct = false): void
    {
        if ($isSingleRequestProduct) {
            return;
        }
        // Get accumulated results from database
        $this->mergeAccumulatedResults();
        
        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) {
                    $resultEmail = $this->apiManager->emailBulkTransferStatus($this->transferResults);
                }
            } catch (\Exception $e) {
                // Silently handle the exception - email failure shouldn't stop the process
                return;
            }
        }
    }
    // phpcs:enable Generic.Metrics.NestingLevel.TooHigh
    
    /**
     * Batch accumulate multiple results in database with single DB call
     *
     * @param string $userEmail
     * @param array $successIds
     * @param array $errorIds
     * @param array $skippedIds
     * @return void
     */
    private function batchAccumulateResults(
        string $userEmail,
        array $successIds = [],
        array $errorIds = [],
        array $skippedIds = []
    ): void {
        try {
            $existingRequest = $this->bulkTransferRequestsManager->getByUser($userEmail);
            if ($existingRequest) {
                $updated = false;
                
                // Merge success IDs
                if (!empty($successIds)) {
                    $currentSuccessIds = $existingRequest->getSuccessIds();
                    // Remove any empty elements
                    $currentSuccessIds = array_filter($currentSuccessIds, function ($id) {
                        return !empty(trim($id));
                    });
                    $newSuccessIds = array_unique(array_merge($currentSuccessIds, $successIds));
                    //implode
                    $newSuccessIds = implode(',', $newSuccessIds);
                    //array to comma separated string
                    $existingRequest->setSuccessIds((string)$newSuccessIds);
                    $updated = true;
                }
                
                // Merge error IDs
                if (!empty($errorIds)) {
                    $currentErrorIds = $existingRequest->getErrorIds();
                    // Remove any empty elements
                    $currentErrorIds = array_filter($currentErrorIds, function ($id) {
                        return !empty(trim($id));
                    });
                    $newErrorIds = array_unique(array_merge($currentErrorIds, $errorIds));
                    //array to comma separated string
                    $existingRequest->setErrorIds(implode(',', $newErrorIds));
                    $updated = true;
                }
                
                // Merge skipped IDs
                if (!empty($skippedIds)) {
                    $currentSkippedIds = $existingRequest->getSkippedIds();
                    // Remove any empty elements
                    $currentSkippedIds = array_filter($currentSkippedIds, function ($id) {
                        return !empty(trim($id));
                    });
                    $newSkippedIds = array_unique(array_merge($currentSkippedIds, $skippedIds));
                    //array to comma separated string
                    $existingRequest->setSkippedIds(implode(',', $newSkippedIds));
                    $updated = true;
                }
                
                // Single database save for all changes
                if ($updated) {
                    // Convert arrays back to strings before saving to database
                    // (getByUser() converts DB strings to arrays, but save() expects strings)
                    $currentData = [
                        'success_ids' => is_array($existingRequest->getSuccessIds()) ?
                            implode(',', $existingRequest->getSuccessIds()) : $existingRequest->getSuccessIds(),
                        'error_ids' => is_array($existingRequest->getErrorIds()) ?
                            implode(',', $existingRequest->getErrorIds()) : $existingRequest->getErrorIds(),
                        'skipped_ids' => is_array($existingRequest->getSkippedIds()) ?
                            implode(',', $existingRequest->getSkippedIds()) : $existingRequest->getSkippedIds(),
                        'queue_ids' => is_array($existingRequest->getQueueIds()) ?
                            implode(',', $existingRequest->getQueueIds()) : $existingRequest->getQueueIds(),
                        'completed_ids' => is_array($existingRequest->getCompletedIds()) ?
                            implode(',', $existingRequest->getCompletedIds()) : $existingRequest->getCompletedIds()
                    ];
                    
                    // Set string values before saving
                    $existingRequest->setSuccessIds($currentData['success_ids']);
                    $existingRequest->setErrorIds($currentData['error_ids']);
                    $existingRequest->setSkippedIds($currentData['skipped_ids']);
                    $existingRequest->setQueueIds($currentData['queue_ids']);
                    $existingRequest->setCompletedIds($currentData['completed_ids']);
                    
                    $this->bulkTransferRequestsManager->update($existingRequest);
                }
            }
        } catch (\Exception $e) {
            // Silently handle the exception - batch accumulation failure shouldn't stop the process
            return;
        }
    }
    
    /**
     * Merge accumulated error and skipped results from database into transfer results
     *
     * @return void
     */
    private function mergeAccumulatedResults(): void
    {
        try {
            $user = $this->apiSession->getCurrentUser();
            $userEmail = $user->getEmail();
            
            $existingRequest = $this->bulkTransferRequestsManager->getByUser($userEmail);
            if ($existingRequest) {
                $successIds = $existingRequest->getSuccessIds() ?: [];
                $errorIds = $existingRequest->getErrorIds() ?: [];
                $skippedIds = $existingRequest->getSkippedIds() ?: [];
                
                // Initialize transfer results if empty
                $this->initializeTransferResults();
                
                $this->transferResults["actorEmail"] = $userEmail;
                if ($existingRequest->getStartedAt()) {
                    $date = new \DateTime($existingRequest->getStartedAt(), new \DateTimeZone('UTC'));
                    $this->transferResults["startedDate"] = $date->format('Y-m-d\TH:i:s');
                }

                // Replace current batch results with ALL accumulated results from database
                // Convert all IDs to strings for consistency
                $this->transferResults['types'][0]['all']['success'] = array_map('strval', $successIds);
                $this->transferResults['types'][0]['all']['failed'] = array_map('strval', $errorIds);
                $this->transferResults['types'][0]['all']['skipped'] = array_map('strval', $skippedIds);
                
                // Also replace store-specific results with accumulated data
                if (isset($this->transferResults['types'][0]['storeIds'])) {
                    foreach ($this->transferResults['types'][0]['storeIds'] as $storeId => &$storeData) {
                        // Replace store results with ALL accumulated results from database
                        $storeData['success'] = array_map('strval', $successIds);
                        $storeData['failed'] = array_map('strval', $errorIds);
                        $storeData['skipped'] = array_map('strval', $skippedIds);
                    }
                }
            }
        } catch (\Exception $e) {
            // Silently handle the exception - merge failure shouldn't stop the email process
            return;
        }
    }

    /**
     * Merge array values
     *
     * @param array $array1
     * @param array $array2
     * @return array
     */
    protected function mergeArrayValues($array1, $array2)
    {
        foreach ($array2 as $value) {
            if (!in_array($value, $array1)) {
                $array1[] = $value;
            }
        }
        return $array1;
    }
}
