<?php
declare(strict_types=1);
namespace Leadlion\Autogedal\Model\Import\Product;

use Firebear\ImportExport\Model\Export\RowCustomizer\ProductVideo;
use Firebear\ImportExport\Model\Source\Type\AbstractType;
use Google\Service\Exception;
use Magento\CatalogImportExport\Model\Import\Product;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\Exception\FileSystemException;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Filesystem\Driver\File;
use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingError;

class ImageProcessor extends \Firebear\ImportExport\Model\Import\Product\ImageProcessor
{
    /**
     * @var \Google_Service_Drive
     */
    private $googleService = null;

    /**
     * @var array
     */
    private $existingImages = [];

    /**
     * @param $obj
     * @param $prop
     *
     * @return mixed
     * @throws \ReflectionException
     */
    private function accessProtectedPropery($obj, $prop)
    {
        $reflection = new \ReflectionClass($obj);
        $property = $reflection->getProperty($prop);
        $property->setAccessible(true);
        return $property->getValue($obj);
    }

    /**
     * @param $obj
     * @param $method
     * @param array $args
     *
     * @return mixed
     * @throws \ReflectionException
     */
    private function accessProtectedMethod($obj, $method, $args = [])
    {
        $reflection = new \ReflectionClass($obj);
        $method = $reflection->getMethod($method);
        $method->setAccessible(true);
        if (count($args) === 0) {
            return $method->invoke($obj);
        }
        return $method->invokeArgs($obj, $args);
    }

    /**
     * Process media gallery data and upload images
     *
     * @param array $rowData
     * @param array $mediaGallery
     * @param array $existingImages
     * @param array $uploadedImages
     * @param int $rowNum
     * @param array $existingAttributeImages
     * @throws Exception
     * @throws FileSystemException
     * @throws LocalizedException
     * @throws \ReflectionException
     */
    public function processMediaGalleryRows(
        &$rowData,
        &$mediaGallery,
        &$existingImages,
        &$uploadedImages,
        $rowNum,
        $existingAttributeImages
    ): void {
        $this->config = $this->accessProtectedPropery($this, 'config');
        $this->mediaDirectory = $this->accessProtectedPropery($this, 'mediaDirectory');
        $this->mediaConfig = $this->accessProtectedPropery($this, 'mediaConfig');
        $this->bunchUploadedImages = $this->accessProtectedPropery($this, 'bunchUploadedImages');

        $disabledImages = [];
        $rowSku = $this->getCorrectSkuAsPerLength($rowData);

        if (!empty($this->config['remove_images']) && array_key_exists($rowSku, $existingImages)
            && empty($rowData[Product::COL_STORE])) {
            $this->removeExistingImages($existingImages[$rowSku], $existingAttributeImages);
            $this->clearPartUploadedImages($uploadedImages, $existingImages[$rowSku]);
            unset($existingImages[$rowSku]);
        }

        if ($this->config['remove_images'] && !empty($rowData[Product::COL_STORE])
            && !empty($existingAttributeImages[$rowData[Product::COL_SKU]]) && !empty($existingImages[$rowSku])) {
            $removeImages = [];
            foreach ($existingImages[$rowSku] as $keyImage => $image) {
                if (empty($existingAttributeImages[$rowData[Product::COL_SKU]][$keyImage])) {
                    $removeImages[$keyImage] = $image;
                    continue;
                }
                $stores = array_flip(array_keys($existingAttributeImages[$rowData[Product::COL_SKU]][$keyImage]));
                unset($stores[$rowData[Product::COL_STORE]]);
                unset($stores['admin']);
                if (!$stores) {
                    $removeImages[$keyImage] = $image;
                }
            }
            if ($removeImages) {
                $this->removeExistingImages($removeImages, $existingAttributeImages);
                $this->clearPartUploadedImages($uploadedImages, $removeImages);
                foreach ($removeImages as $key => $removeImage) {
                    unset($existingImages[$rowSku][$key]);
                }
            }
        }

        if (empty($this->config['remove_images'])) {
            $nonExistentImages = [];
            if (array_key_exists($rowSku, $existingImages)) {
                foreach ($existingImages[$rowSku] as $keyImage => $image) {
                    if (is_array($image) && !empty($image['value'])) {
                        $mediaFile = $image['value'];
                    } else {
                        $mediaFile = $keyImage;
                    }

                    $filePath = $this->getMediaFilePath($mediaFile);

                    if (!$this->mediaDirectory->isExist($filePath)) {
                        $nonExistentImages[$keyImage] = $image;
                        unset($existingImages[$rowSku][$keyImage]);
                    }
                }
            }
            if (!empty($nonExistentImages)) {
                $this->removeExistingImages($nonExistentImages, $existingAttributeImages);
                $this->clearPartUploadedImages($uploadedImages, $nonExistentImages);
            }
        }

        $rowData = $this->checkAdditionalImages($rowData);
        if (isset($this->config['source_type']) && $this->config['source_type'] === 'rest') {
            unset($rowData['additional_images']);
        }

        if (isset($rowData['image']) && !empty($this->config['copy_base_image'])) {
            if (!isset($rowData['thumbnail'])) {
                $rowData['thumbnail'] = $rowData['image'];
            }
            if (!isset($rowData['small_image'])) {
                $rowData['small_image'] = $rowData['image'];
            }
        }

        list($rowImages, $rowLabels) = $this->getImagesFromRow($rowData);

        if (isset($rowData['_media_is_disabled'])) {
            $disabledImages = array_flip(
                explode($this->getMultipleValueSeparator(), $rowData['_media_is_disabled'])
            );
        }

        $rowData[Product::COL_MEDIA_IMAGE] = [];
        $imagePosition = $this->getMaxImagePosition($existingImages, $rowSku);

        foreach ($rowImages as $column => $columnImages) {
            foreach ($columnImages as $position => $columnImage) {
                try {
                    $uploadedFile = '';
                    $originalColumnImage = $columnImage;

                    if (isset($uploadedImages[$columnImage])) {
                        $cachedPath = $this->mediaConfig->getMediaPath($uploadedImages[$columnImage]);
                        if ($this->verifyFileIntegrity($cachedPath)) {
                            $uploadedFile = $uploadedImages[$columnImage];
                            $this->_logger->debug("Using cached image: {$uploadedFile} for {$columnImage}");
                        } else {
                            unset($uploadedImages[$columnImage]);
                        }
                    }

                    if (!$uploadedFile) {
                        if (preg_match('/(drive.google.com|googleapis)/', $columnImage)) {
                            $this->_logger->info('Downloading from Drive: ' . $columnImage);
                            $downloadedPath = $this->downloadFromDrive($columnImage, $rowSku);

                            if ($downloadedPath) {
                                $tmpPath = $this->mediaDirectory->getAbsolutePath() .
                                    AbstractType::MEDIA_IMPORT_DIR .
                                    $downloadedPath;

                                if (!$this->mediaDirectory->isExist($tmpPath)) {
                                    $this->_logger->error("Downloaded file not found in tmp: {$tmpPath}");
                                    continue;
                                }

                                $downloadedHash = hash_file('md5', $tmpPath);
                                $this->_logger->debug("Downloaded file hash for {$downloadedPath}: {$downloadedHash}");

                                // Check if file exists in media directory
                                $potentialMediaPath = $this->mediaConfig->getMediaPath($downloadedPath);
                                if ($this->mediaDirectory->isExist($potentialMediaPath)) {
                                    $existingHash = hash_file('md5', $this->mediaDirectory->getAbsolutePath($potentialMediaPath));

                                    if ($existingHash !== $downloadedHash) {
                                        $this->_logger->warning(
                                            "File {$downloadedPath} already exists with different content. Skipping move operation."
                                        );
                                        continue;
                                    }
                                }

                                $columnImage = $downloadedPath;
                            } else {
                                $this->_logger->error("Failed to download image: {$columnImage}");
                                continue;
                            }
                        }

                        $renameFileOff = !empty($this->config['keep_uploaded_image_name']);
                        $uploadedFile = $this->uploadMediaFiles(trim($columnImage), $renameFileOff);

                        if ($uploadedFile) {
                            $finalPath = $this->mediaConfig->getMediaPath($uploadedFile);
                            $this->_logger->debug("File uploaded to: {$finalPath}");

                            // Store file information
                            $uploadedImages[$columnImage] = $uploadedFile;
                            $uploadedImages[$originalColumnImage] = $uploadedFile;

                            if ($this->mediaDirectory->isExist($finalPath)) {
                                $contentHash = hash_file('md5', $this->mediaDirectory->getAbsolutePath($finalPath));
                                $this->existingImages[basename($uploadedFile)] = [
                                    'hash' => $contentHash,
                                    'original_path' => $columnImage
                                ];
                            }
                        } else {
                            $this->addRowError(
                                sprintf(
                                    (string)__('Wrong URL/path used for attribute %s For SKU : "%s" in rows'),
                                    $column,
                                    $rowData['sku']
                                ),
                                $rowData['rowNum'] ?? $rowNum,
                                null,
                                null,
                                ProcessingError::ERROR_LEVEL_WARNING
                            );
                            $rowData[$column] = null;
                            continue;
                        }
                    }

                    if ($uploadedFile && $column !== Product::COL_MEDIA_IMAGE) {
                        $rowData[$column] = $uploadedFile;
                    }

                    $imageNotAssigned = !isset($existingImages[$rowSku][$uploadedFile]);
                    $this->bunchUploadedImages[$columnImage] = $uploadedFile;

                    if ($uploadedFile && $imageNotAssigned) {
                        if ($column == Product::COL_MEDIA_IMAGE) {
                            $rowData[$column][] = $uploadedFile;
                        }

                        $pos = ++$imagePosition;
                        $mediaData = [
                            'attribute_id' => $this->getMediaGalleryAttributeId(),
                            'label' => $rowLabels[$column][$position] ?? '',
                            'position' => $pos,
                            'disabled' => isset($disabledImages[$columnImage]) ? '1' : '0',
                            'value' => $uploadedFile
                        ];

                        $uploader = $this->accessProtectedMethod($this, 'getUploader');
                        if ($uploader->checkValidUrl($columnImage)) {
                            $mediaData[ProductVideo::VIDEO_URL_COLUMN] = $columnImage;
                        }

                        $this->accessProtectedMethod($this, 'prepareMediaGallery', [$mediaData, &$mediaGallery, $rowData]);
                        $existingImages[$rowSku][$uploadedFile] = true;
                    } elseif (!empty($existingImages[$rowSku][$uploadedFile]['value_id'])) {
                        $this->updateExistingImage(
                            $rowData,
                            $existingImages[$rowSku][$uploadedFile],
                            $rowLabels[$column][$position] ?? '',
                            $disabledImages[$columnImage] ?? false,
                            $imagePosition,
                            $mediaGallery
                        );
                    }

                } catch (\Exception $e) {
                    $this->_logger->error("Error processing image: " . $e->getMessage());
                    $uploadedFile = '';
                }
            }
        }
    }

    /**
     * Verify file integrity
     *
     * @param string $path
     * @param string|null $expectedHash
     * @return bool
     */
    private function verifyFileIntegrity(string $path, ?string $expectedHash = null): bool
    {
        try {
            $fullPath = $this->mediaDirectory->getAbsolutePath($path);
            if (!$this->mediaDirectory->isExist($fullPath)) {
                return false;
            }

            if ($expectedHash) {
                $currentHash = hash_file('md5', $fullPath);
                if ($currentHash !== $expectedHash) {
                    $this->_logger->error("File content mismatch for {$path}");
                    return false;
                }
            }

            return true;
        } catch (\Exception $e) {
            $this->_logger->error("Error verifying file integrity: " . $e->getMessage());
            return false;
        }
    }

    /**
     * Get maximum image position
     *
     * @param array $existingImages
     * @param string $rowSku
     * @return int
     */
    private function getMaxImagePosition(array $existingImages, string $rowSku): int
    {
        $imagePosition = 0;
        if (isset($existingImages[$rowSku])) {
            $existingProductImages = $existingImages[$rowSku];
            $existingImagesPosition = array_map(
                function ($value) {
                    if (is_array($value)) {
                        return (int)($value['position'] ?? 0);
                    }
                    return 0;
                },
                $existingProductImages
            );
            if (!empty($existingImagesPosition)) {
                $imagePosition = (int)max($existingImagesPosition);
            }
        }
        return (int)$imagePosition;
    }

    /**
     * Get media file path
     *
     * @param string $mediaFile
     * @return string
     */
    private function getMediaFilePath(string $mediaFile): string
    {
        if (is_a($this->mediaDirectory->getDriver(), File::class)) {
            return $this->mediaDirectory->getAbsolutePath(
                '/pub/' . DirectoryList::MEDIA . DIRECTORY_SEPARATOR .
                $this->mediaConfig->getMediaPath($mediaFile)
            );
        }
        return $this->mediaDirectory->getAbsolutePath(
            DirectoryList::MEDIA . DIRECTORY_SEPARATOR .
            $this->mediaConfig->getMediaPath($mediaFile)
        );
    }

    /**
     * Update existing image
     *
     * @param array $rowData
     * @param array $existingImage
     * @param string $newLabel
     * @param bool $isDisabled
     * @param int $position
     * @param array $mediaGallery
     * @throws \ReflectionException
     */
    private function updateExistingImage(
        array $rowData,
        array $existingImage,
        string $newLabel,
        bool $isDisabled,
        int $position,
        array &$mediaGallery
    ): void {
        $storeViewCode = $rowData[Product::COL_STORE_VIEW_CODE] ?? 'admin';
        $storeId = $this->accessProtectedMethod($this, 'getStoreIdByCode', [$storeViewCode]) ?? 0;
        $createNewLabel = empty($existingImage['create_new_label']);

        if ($newLabel) {
            $linkField = $this->accessProtectedMethod($this, 'getProductEntityLinkField');
            $mediaData = [
                'label' => $newLabel,
                'position' => $existingImage['position'] ?? $position,
                'disabled' => $isDisabled ? '1' : '0',
                'value_id' => $existingImage['value_id'],
                'store_id' => $storeId,
                'create_new_label' => $createNewLabel,
                $linkField => $existingImage[$linkField]
            ];
            $this->accessProtectedMethod($this, 'prepareMediaGallery', [$mediaData, &$mediaGallery, $rowData]);
            $existingImage['create_new_label'] = true;
        }
    }

    /**
     * Connect to google drive only once
     *
     * @return void
     * @throws LocalizedException
     */
    private function connectToGoogleDrive(): void
    {
        if ($this->googleService == null) {
            $signingKeyFilePath = BP . '/pub/media/importexport/a/u/autogedal-340216-786dbe024fc8_1.json';

            if (is_string($signingKeyFilePath)) {
                if (!is_readable($signingKeyFilePath)) {
                    $errorMessage = 'The Service Account Key file does not exist or does not have enough permissions';
                    throw new LocalizedException(__($errorMessage));
                }
                $signingKeyStream = file_get_contents($signingKeyFilePath);
                if (!$signingKey = json_decode($signingKeyStream, true)) {
                    throw new \LogicException('The Service Account Key json is invalid');
                }
                $credentials = [
                    'use_application_default_credentials' => true,
                    'client_id' => $signingKey['client_id'],
                    'client_email' => $signingKey['client_email'],
                    'signing_key' => $signingKey['private_key']
                ];
                $client = new \Google_Client($credentials);
                $client->addScope("https://www.googleapis.com/auth/drive");
                $this->googleService = new \Google_Service_Drive($client);
            }
        }
    }


    /**
     * @param $link
     * @param $rowSku
     *
     * @return string
     * @throws LocalizedException
     */
    private function downloadFromDrive($link, $rowSku): string
    {
        preg_match('/(?:\/d\/|[?&]id=)([\w-]+)/', $link, $matches);
        $linkId = $matches[1] ?? '';

        if (!$linkId) {
            preg_match('/^([\w-]{25,})/', $link, $matches);
            $linkId = $matches[1] ?? '';
        }

        $this->_logger->debug((string)$rowSku . ' link id: ' . $linkId);

        if (isset($this->existingImages[$linkId])) {
            $this->_logger->debug('Using cached path for link id: ' . $linkId);
            return $this->existingImages[$linkId];
        }

        try {
            $this->connectToGoogleDrive();

            if (!$linkId || !$this->googleService) {
                $this->_logger->error('Invalid link id or Google service not available for product ' . (string)$rowSku);
                return '';
            }

            $fileInfo = $this->googleService->files->get($linkId);
            $name = $fileInfo->getName();

            if (!pathinfo($name, PATHINFO_EXTENSION)) {
                $extension = $fileInfo->getFileExtension();
                $name .= '.' . $extension;
            }

            $relativePath = AbstractType::MEDIA_IMPORT_DIR . DIRECTORY_SEPARATOR . $name;

            if ($this->mediaDirectory->isExist($relativePath)) {
                $this->_logger->debug('File exists in tmp directory: ' . $name);
                $this->existingImages[$linkId] = DIRECTORY_SEPARATOR . $name;
                return DIRECTORY_SEPARATOR . $name;
            }

            $this->_logger->debug('Downloading file: ' . $name . ' for SKU: ' . $rowSku);

            try {
                $absolutePath = $this->mediaDirectory->getAbsolutePath($relativePath);
                $downloadSuccess = $this->downloadWithCurl($linkId, $absolutePath);

                if (!$downloadSuccess) {
                    $this->_logger->debug('cURL download failed, trying Google Drive API...');
                    $file = $this->googleService->files->get($linkId, array('alt' => 'media'));
                    $this->mediaDirectory->writeFile($relativePath, $file->getBody());
                }

                if ($this->mediaDirectory->isExist($relativePath)) {
                    $this->_logger->debug('File successfully written to tmp directory: ' . $name);
                    $this->existingImages[$linkId] = DIRECTORY_SEPARATOR . $name;
                    return DIRECTORY_SEPARATOR . $name;
                }
            } catch (\Exception $e) {
                $this->_logger->error('Download failed for file: ' . $name . ' for product ' . (string)$rowSku);
                $this->_logger->error($e->getMessage());
                return '';
            }

        } catch (\Exception $e) {
            $this->_logger->error('Error processing link id ' . $linkId . ' for product ' . (string)$rowSku);
            $this->_logger->error($e->getMessage());
            return '';
        }

        return '';
    }

    /**
     * Attempt to download file using cURL
     *
     * @param string $fileId
     * @param string $savePath
     * @return bool
     */
    private function downloadWithCurl($fileId, $savePath): bool
    {
        try {
            $accessToken = $this->googleService->getClient()->getAccessToken();
            if (!isset($accessToken['access_token'])) {
                return false;
            }

            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, "https://www.googleapis.com/drive/v3/files/{$fileId}?alt=media");
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
            curl_setopt($ch, CURLOPT_HEADER, 0);
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_HTTPHEADER, [
                'Authorization: Bearer ' . $accessToken['access_token']
            ]);

            $fp = fopen($savePath, 'wb');
            curl_setopt($ch, CURLOPT_FILE, $fp);

            $success = curl_exec($ch);
            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

            fclose($fp);
            curl_close($ch);

            return $success && $httpCode === 200;
        } catch (\Exception $e) {
            $this->_logger->warning('cURL download failed: ' . $e->getMessage());
            return false;
        }
    }
}
