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

declare(strict_types=1);

namespace WriteTextAI\WriteTextAI\Service;

use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Model\ResourceModel\Product\Gallery;
use Magento\Framework\DB\Adapter\AdapterInterface;
use Magento\Store\Model\Store;
use Psr\Log\LoggerInterface;

class ImageAltTextTransfer
{
    /**
     * @var Gallery
     */
    private $productGalleryResource;

    /**
     * @var LoggerInterface
     */
    private $logger;

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

    /**
     * @var array|null
     */
    private $tableColumns;

    /**
     * @var array|null
     */
    private $copyableColumns;

    /**
     * @param Gallery $productGalleryResource
     * @param LoggerInterface $logger
     * @param ProductRepositoryInterface $productRepository
     */
    public function __construct(
        Gallery $productGalleryResource,
        LoggerInterface $logger,
        ProductRepositoryInterface $productRepository
    ) {
        $this->productGalleryResource = $productGalleryResource;
        $this->logger = $logger;
        $this->productRepository = $productRepository;
    }

    /**
     * Persist image alt text labels per store directly via DB.
     *
     * @param array $storeImages [storeId => [valueId => label]]
     * @return void
     */
    public function updateStoreImageLabels(array $storeImages): void
    {
        $rows = $this->prepareRows($storeImages);
        if (empty($rows)) {
            return;
        }

        $connection = $this->getConnection();
        $tableName = $this->productGalleryResource->getTable(Gallery::GALLERY_VALUE_TABLE);

        $connection->beginTransaction();
        try {
            $connection->insertOnDuplicate($tableName, $rows, ['label']);
            $connection->commit();
            $this->cleanProductRepositoryCache();
        } catch (\Throwable $exception) {
            $connection->rollBack();
            $this->logger->error(
                'Failed to update image alt text labels',
                ['exception' => $exception]
            );
            throw $exception;
        }
    }

    /**
     * Clear product repository cache to expose fresh gallery labels.
     *
     * @return void
     */
    private function cleanProductRepositoryCache(): void
    {
        if (method_exists($this->productRepository, 'cleanCache')) {
            $this->productRepository->cleanCache();
        }
    }

    /**
     * Prepare rows for insert/update.
     *
     * @param array $storeImages
     * @return array
     */
    private function prepareRows(array $storeImages): array
    {
        $flatUpdates = [];
        foreach ($storeImages as $storeId => $images) {
            if (!is_array($images)) {
                continue;
            }
            foreach ($images as $valueId => $label) {
                if ($label === null || $label === '') {
                    continue;
                }
                $flatUpdates[] = [
                    'value_id' => (int)$valueId,
                    'store_id' => (int)$storeId,
                    'label' => $label
                ];
            }
        }

        if (empty($flatUpdates)) {
            return [];
        }

        $valueIds = array_column($flatUpdates, 'value_id');
        $uniqueValueIds = array_values(array_unique($valueIds));
        $valueMetadata = $this->fetchValueMetadata($uniqueValueIds);

        $rows = [];
        $missingValueIds = [];
        $copyableColumns = $this->getCopyableColumns();

        foreach ($flatUpdates as $update) {
            $valueId = $update['value_id'];
            if (!isset($valueMetadata[$valueId]['entity_id'])) {
                if (!in_array($valueId, $missingValueIds, true)) {
                    $missingValueIds[] = $valueId;
                }
                continue;
            }

            $storeId = $update['store_id'];
            $storeData = $valueMetadata[$valueId]['stores'][$storeId] ?? null;
            $isFallback = false;
            if ($storeData === null) {
                $storeData = $valueMetadata[$valueId]['stores'][Store::DEFAULT_STORE_ID] ?? [];
                $isFallback = true;
            }

            $additionalColumns = array_intersect_key($storeData, $copyableColumns);
            if ($isFallback) {
                unset($additionalColumns['record_id']);
            }

            $rows[] = [
                'value_id' => $valueId,
                'store_id' => $storeId,
                'entity_id' => $valueMetadata[$valueId]['entity_id'],
                'label' => $update['label'],
                'record_id' => $isFallback ? null : ($storeData['record_id'] ?? null)
            ] + $additionalColumns;
        }

        if (!empty($missingValueIds)) {
            $this->logger->warning(
                'Unable to resolve entity IDs for image value IDs when updating alt text',
                ['value_ids' => $missingValueIds]
            );
        }

        return $rows;
    }

    /**
     * Fetch value metadata (entity/link + store-specific columns).
     *
     * @param array $valueIds
     * @return array
     */
    private function fetchValueMetadata(array $valueIds): array
    {
        if (empty($valueIds)) {
            return [];
        }

        $connection = $this->getConnection();
        $tableName = $this->productGalleryResource->getTable(Gallery::GALLERY_VALUE_TABLE);
        $select = $connection->select()
            ->from($tableName, '*')
            ->where('value_id IN (?)', $valueIds);

        $rows = $connection->fetchAll($select);

        $metadata = [];
        foreach ($rows as $row) {
            $valueId = (int)$row['value_id'];
            $storeId = (int)$row['store_id'];
            $entityId = (int)$row['entity_id'];

            if (!isset($metadata[$valueId]['entity_id'])) {
                $metadata[$valueId]['entity_id'] = $entityId;
            }

            if ($storeId === Store::DEFAULT_STORE_ID) {
                $metadata[$valueId]['entity_id'] = $entityId;
            }

            $metadata[$valueId]['stores'][$storeId] = $this->filterStoreRow($row);
        }

        return $metadata;
    }

    /**
     * Filter table row to only copy allowed columns.
     *
     * @param array $row
     * @return array
     */
    private function filterStoreRow(array $row): array
    {
        $copyableColumns = $this->getCopyableColumns();
        $filtered = array_intersect_key($row, $copyableColumns);

        if (array_key_exists('record_id', $row)) {
            $filtered['record_id'] = $row['record_id'] !== null ? (int)$row['record_id'] : null;
        }

        return $filtered;
    }

    /**
     * Get copyable columns for gallery value table.
     *
     * @return array
     */
    private function getCopyableColumns(): array
    {
        if ($this->copyableColumns !== null) {
            return $this->copyableColumns;
        }

        $columns = $this->getTableColumns();
        $excluded = ['value_id', 'store_id', 'entity_id', 'label', 'record_id'];
        $copyable = array_diff($columns, $excluded);

        $this->copyableColumns = array_fill_keys($copyable, true);

        return $this->copyableColumns;
    }

    /**
     * Retrieve gallery value table columns.
     *
     * @return array
     */
    private function getTableColumns(): array
    {
        if ($this->tableColumns === null) {
            $connection = $this->getConnection();
            $tableName = $this->productGalleryResource->getTable(Gallery::GALLERY_VALUE_TABLE);
            $this->tableColumns = array_keys($connection->describeTable($tableName));
        }

        return $this->tableColumns;
    }

    /**
     * @return AdapterInterface
     */
    private function getConnection(): AdapterInterface
    {
        return $this->productGalleryResource->getConnection();
    }
}
