<?php

namespace App\Services;

use Cart\Currency;
use Cart\Tax;
use Exception;
use Openguru\OpenCartFramework\CriteriaBuilder\CriteriaBuilder;
use Openguru\OpenCartFramework\Exceptions\EntityNotFoundException;
use Openguru\OpenCartFramework\ImageTool\ImageFactory;
use Openguru\OpenCartFramework\ImageTool\ImageNotFoundException;
use Openguru\OpenCartFramework\ImageTool\ImageUtils;
use Openguru\OpenCartFramework\OpenCart\Decorators\OcRegistryDecorator;
use Openguru\OpenCartFramework\OpenCart\PriceCalculator;
use Openguru\OpenCartFramework\QueryBuilder\Builder;
use Openguru\OpenCartFramework\QueryBuilder\JoinClause;
use Openguru\OpenCartFramework\QueryBuilder\RawExpression;
use Openguru\OpenCartFramework\QueryBuilder\Table;
use Openguru\OpenCartFramework\Support\Arr;
use Openguru\OpenCartFramework\Support\PaginationHelper;
use Openguru\OpenCartFramework\Support\Str;
use Psr\Log\LoggerInterface;

class ProductsService
{
    private Builder $queryBuilder;
    private Currency $currency;
    private Tax $tax;
    private SettingsService $settings;
    private ImageFactory $image;
    private OcRegistryDecorator $oc;
    private LoggerInterface $logger;
    private CriteriaBuilder $criteriaBuilder;
    private PriceCalculator $priceCalculator;

    public function __construct(
        Builder $queryBuilder,
        Currency $currency,
        Tax $tax,
        SettingsService $settings,
        ImageFactory $image,
        OcRegistryDecorator $registry,
        LoggerInterface $logger,
        CriteriaBuilder $criteriaBuilder,
        PriceCalculator $priceCalculator
    ) {
        $this->queryBuilder = $queryBuilder;
        $this->currency = $currency;
        $this->tax = $tax;
        $this->settings = $settings;
        $this->image = $image;
        $this->oc = $registry;
        $this->logger = $logger;
        $this->criteriaBuilder = $criteriaBuilder;
        $this->priceCalculator = $priceCalculator;
    }

    /**
     * @throws ImageNotFoundException
     */
    public function getProductsResponse(array $params, int $languageId, int $storeId): array
    {
        $page = $params['page'];
        $perPage = $params['perPage'];
        $search = $params['search'] ?? false;
        $categoryName = '';
        $maxPages = 200;
        $filters = $params['filters'] ?? [];

        $aspectRatio = $this->settings->get('app.image_aspect_ratio', '1:1');
        $cropAlgorithm = $this->settings->get('app.image_crop_algorithm', 'cover');

        [$imageWidth, $imageHeight] = ImageUtils::aspectRatioToSize($aspectRatio);

        $customerGroupId = $this->settings->config()->getOrders()->getOcCustomerGroupId();
        $currency = $this->settings->config()->getStore()->getOcDefaultCurrency();

        $specialPriceSql = "(SELECT price
                 FROM oc_product_special ps
                 WHERE ps.product_id = products.product_id
                   AND ps.customer_group_id = $customerGroupId
                   AND ((ps.date_start = '0000-00-00' OR ps.date_start < NOW()) AND
                        (ps.date_end = '0000-00-00' OR ps.date_end > NOW()))
                 ORDER BY ps.priority ASC, ps.price ASC
                 LIMIT 1) AS special";

        $productsQuery = $this->queryBuilder->newQuery()
            ->select([
                'products.product_id' => 'product_id',
                'products.quantity' => 'product_quantity',
                'product_description.name' => 'product_name',
                'products.price' => 'price',
                'products.image' => 'product_image',
                'products.tax_class_id' => 'tax_class_id',
                'manufacturer.name' => 'manufacturer_name',
                'category_description.name' => 'category_name',
                new RawExpression($specialPriceSql),
            ])
            ->from(db_table('product'), 'products')
            ->join(
                db_table('product_description') . ' AS product_description',
                function (JoinClause $join) use ($languageId) {
                    $join->on('products.product_id', '=', 'product_description.product_id')
                        ->where('product_description.language_id', '=', $languageId);
                }
            )
            ->join(
                new Table(db_table('product_to_store'), 'product_to_store'),
                function (JoinClause $join) use ($storeId) {
                    $join->on('product_to_store.product_id', '=', 'products.product_id')
                        ->where('product_to_store.store_id', '=', $storeId);
                }
            )
            ->leftJoin(new Table(db_table('manufacturer'), 'manufacturer'), function (JoinClause $join) {
                $join->on('products.manufacturer_id', '=', 'manufacturer.manufacturer_id');
            })
            ->leftJoin(new Table(db_table('product_to_category'), 'product_to_category'), function (JoinClause $join) {
                $join->on('products.product_id', '=', 'product_to_category.product_id')
                    ->where('product_to_category.main_category', '=', 1);
            })
            ->leftJoin(
                new Table(db_table('category_description'), 'category_description'),
                function (JoinClause $join) use ($languageId) {
                    $join->on('product_to_category.category_id', '=', 'category_description.category_id')
                        ->where('category_description.language_id', '=', $languageId);
                }
            )
            ->where('products.status', '=', 1)
            ->whereRaw('products.date_available < NOW()')
            ->when($search, function (Builder $query) use ($search) {
                $query->where('product_description.name', 'LIKE', '%' . $search . '%');
            });

        $this->criteriaBuilder->apply($productsQuery, $filters);

        $total = $productsQuery->count();
        $lastPage = min(PaginationHelper::calculateLastPage($total, $perPage), $maxPages);
        $hasMore = $page + 1 <= $lastPage;

        $products = $productsQuery
            ->forPage($page, $perPage)
            ->orderBy('date_modified', 'DESC')
            ->get();

        $productIds = Arr::pluck($products, 'product_id');
        $productsImages = [];

        if ($productIds) {
            $productsImages = $this->queryBuilder->newQuery()
                ->select([
                    'products_images.product_id' => 'product_id',
                    'products_images.image' => 'image',
                ])
                ->from(db_table('product_image'), 'products_images')
                ->orderBy('products_images.sort_order')
                ->whereIn('product_id', $productIds)
                ->get();
        }

        $productsImagesMap = [];
        foreach ($productsImages as $item) {
            $productId = $item['product_id'];

            // Ограничиваем количество картинок для каждого товара до 3
            if (! isset($productsImagesMap[$productId])) {
                $productsImagesMap[$productId] = [];
            }

            if (count($productsImagesMap[$productId]) < 2) {
                $productsImagesMap[$productId][] = [
                    'url' => $this->image->make($item['image'])->crop($cropAlgorithm, $imageWidth, $imageHeight)->url(),
                    'alt' => 'Product Image',
                ];
            }
        }

        $debug = [];
        if (env('APP_DEBUG')) {
            $debug = [
                'sql' => $productsQuery->toRawSql(),
            ];
        }

        return [
            'data' => array_map(
                function ($product) use ($productsImagesMap, $cropAlgorithm, $imageWidth, $imageHeight, $currency) {
                    $allImages = [];

                    $image = $this->image->make($product['product_image'], false)
                        ->crop($cropAlgorithm, $imageWidth, $imageHeight)
                        ->url();

                    $allImages[] = [
                        'url' => $image,
                        'alt' => Str::htmlEntityEncode($product['product_name']),
                    ];

                    $price = $this->priceCalculator->format($product['price'], $product['tax_class_id']);
                    $priceNumeric = $this->priceCalculator->getPriceNumeric(
                        $product['price'],
                        $product['tax_class_id']
                    );

                    $special = false;
                    $specialPriceNumeric = null;
                    if ($product['special'] && (float) $product['special'] >= 0) {
                        $specialPriceNumeric = $this->tax->calculate(
                            $product['special'],
                            $product['tax_class_id'],
                            $this->settings->config()->getStore()->isOcConfigTax(),
                        );
                        $special = $this->currency->format(
                            $specialPriceNumeric,
                            $currency,
                        );
                    }

                    if (! empty($productsImagesMap[$product['product_id']])) {
                        $allImages = array_merge($allImages, $productsImagesMap[$product['product_id']]);
                    }

                    return [
                        'id' => (int) $product['product_id'],
                        'product_quantity' => (int) $product['product_quantity'],
                        'name' => Str::htmlEntityEncode($product['product_name']),
                        'price' => $price,
                        'special' => $special,
                        'image' => $image,
                        'images' => $allImages,
                        'special_numeric' => $specialPriceNumeric,
                        'price_numeric' => $priceNumeric,
                        'final_price_numeric' => $specialPriceNumeric ?: $priceNumeric,
                        'manufacturer_name' => $product['manufacturer_name'],
                        'category_name' => $product['category_name'],
                    ];
                },
                $products
            ),

            'meta' => [
                'currentCategoryName' => $categoryName,
                'hasMore' => $hasMore,
                'debug' => $debug,
                'total' => $total,
            ]
        ];
    }

    /**
     * @throws EntityNotFoundException
     * @throws Exception
     */
    public function getProductById(int $productId): array
    {
        $this->oc->load->language('product/product');
        $this->oc->load->model('catalog/category');
        $this->oc->load->model('catalog/manufacturer');
        $this->oc->load->model('catalog/product');
        $this->oc->load->model('catalog/review');
        $this->oc->load->model('tool/image');

        $configTax = $this->oc->config->get('config_tax');

        $product_info = $this->oc->model_catalog_product->getProduct($productId);
        $currency = $this->oc->session->data['currency'];

        if (! $product_info) {
            throw new EntityNotFoundException('Product with id ' . $productId . ' not found');
        }

        $data = [];
        $data['text_minimum'] = sprintf($this->oc->language->get('text_minimum'), $product_info['minimum']);

        $data['tab_review'] = sprintf($this->oc->language->get('tab_review'), $product_info['reviews']);

        $data['product_id'] = $productId;
        $data['name'] = Str::htmlEntityEncode($product_info['name']);
        $data['manufacturer'] = $product_info['manufacturer'];
        $data['model'] = $product_info['model'];
        $data['reward'] = $product_info['reward'];
        $data['points'] = (int) $product_info['points'];
        $data['description'] = Str::htmlEntityEncode($product_info['description']);
        $data['share'] = Str::htmlEntityEncode(
            $this->oc->url->link('product/product', [
                'product_id' => $productId,
                'utm_source' => 'telecart',
                'utm_medium' => 'telegram',
                'utm_campaign' => 'product_click',
                'utm_content' => 'product_button',
            ]),
        );

        if ($product_info['quantity'] <= 0) {
            $data['stock'] = $product_info['stock_status'];
        } elseif ($this->oc->config->get('config_stock_display')) {
            $data['stock'] = $product_info['quantity'];
        } else {
            $data['stock'] = $this->oc->language->get('text_instock');
        }

        $data['images'] = [];

        $price = $this->priceCalculator->format($product_info['price'], $product_info['tax_class_id']);
        $priceNumeric = $this->priceCalculator->getPriceNumeric($product_info['price'], $product_info['tax_class_id']);

        $data['price'] = $price;
        $data['currency'] = $currency;
        $data['final_price_numeric'] = $priceNumeric;

        if (! is_null($product_info['special']) && (float) $product_info['special'] >= 0) {
            $productSpecialPrice = $this->tax->calculate(
                $product_info['special'],
                $product_info['tax_class_id'],
                $configTax,
            );

            $data['special'] = $this->currency->format($productSpecialPrice, $currency);
            $data['final_price_numeric'] = $productSpecialPrice;
            $tax_price = (float) $product_info['special'];
        } else {
            $data['special'] = false;
            $tax_price = (float) $product_info['price'];
        }

        if ($configTax) {
            $data['tax'] = $this->currency->format($tax_price, $currency);
        } else {
            $data['tax'] = false;
        }

        $discounts = $this->oc->model_catalog_product->getProductDiscounts($productId);

        $data['discounts'] = [];

        foreach ($discounts as $discount) {
            $data['discounts'][] = array(
                'quantity' => $discount['quantity'],
                'price' => $this->currency->format(
                    $this->tax->calculate(
                        $discount['price'],
                        $product_info['tax_class_id'],
                        $configTax,
                    ),
                    $currency
                )
            );
        }

        $data['options'] = [];

        foreach ($this->oc->model_catalog_product->getProductOptions($productId) as $option) {
            $product_option_value_data = [];

            foreach ($option['product_option_value'] as $option_value) {
                if (! $option_value['subtract'] || ($option_value['quantity'] > 0)) {
                    $price = $this->currency->format(
                        $this->tax->calculate(
                            $option_value['price'],
                            $product_info['tax_class_id'],
                            $configTax ? 'P' : false
                        ),
                        $currency
                    );

                    $product_option_value_data[] = array(
                        'product_option_value_id' => (int) $option_value['product_option_value_id'],
                        'option_value_id' => (int) $option_value['option_value_id'],
                        'name' => $option_value['name'],
                        'image' => $this->oc->model_tool_image->resize($option_value['image'], 50, 50),
                        'price' => $price,
                        'price_prefix' => $option_value['price_prefix'],
                        'selected' => false,
                    );
                }
            }

            $data['options'][] = array(
                'product_option_id' => $option['product_option_id'],
                'product_option_value' => $product_option_value_data,
                'option_id' => $option['option_id'],
                'name' => $option['name'],
                'type' => $option['type'],
                'value' => $option['value'],
                'required' => filter_var($option['required'], FILTER_VALIDATE_BOOLEAN),
            );
        }

        if ($product_info['minimum']) {
            $data['minimum'] = (int) $product_info['minimum'];
        } else {
            $data['minimum'] = 1;
        }

        $data['review_status'] = $this->oc->config->get('config_review_status');

        $data['review_guest'] = true;

        $data['customer_name'] = 'John Doe';

        $data['reviews'] = sprintf($this->oc->language->get('text_reviews'), (int) $product_info['reviews']);
        $data['rating'] = (int) $product_info['rating'];

        $data['attribute_groups'] = $this->oc->model_catalog_product->getProductAttributes($productId);

        $data['tags'] = array();

        if ($product_info['tag']) {
            $tags = explode(',', $product_info['tag']);

            foreach ($tags as $tag) {
                $data['tags'][] = array(
                    'tag' => trim($tag),
                    'href' => $this->oc->url->link('product/search', 'tag=' . trim($tag))
                );
            }
        }

        $data['recurrings'] = $this->oc->model_catalog_product->getProfiles($productId);

        $data['category'] = $this->getProductMainCategory($productId);
        $data['id'] = $productId;

        $this->oc->model_catalog_product->updateViewed($productId);

        return $data;
    }

    private function getProductMainCategory(int $productId): ?array
    {
        return $this->queryBuilder->newQuery()
            ->select([
                'category_description.category_id' => 'id',
                'category_description.name' => 'name',
            ])
            ->from(db_table('category_description'), 'category_description')
            ->join(new Table(db_table('product_to_category'), 'product_to_category'), function (JoinClause $join) {
                $join->on('product_to_category.category_id', '=', 'category_description.category_id')
                    ->where('product_to_category.main_category', '=', 1);
            })
            ->where('product_to_category.product_id', '=', $productId)
            ->firstOrNull();
    }

    public function getProductImages(int $productId): array
    {
        $aspectRatio = $this->settings->get('app.image_aspect_ratio', '1:1');
        $cropAlgorithm = $this->settings->get('app.image_crop_algorithm', 'cover');

        [$imageWidth, $imageHeight] = ImageUtils::aspectRatioToSize($aspectRatio);

        $imageFullWidth = 1000;
        $imageFullHeight = 1000;

        $product_info = $this->oc->model_catalog_product->getProduct($productId);

        if (! $product_info) {
            throw new EntityNotFoundException('Product with id ' . $productId . ' not found');
        }

        $allImages = [];
        if ($product_info['image']) {
            $allImages[] = $product_info['image'];
        }

        $results = $this->oc->model_catalog_product->getProductImages($productId);
        foreach ($results as $result) {
            $allImages[] = $result['image'];
        }

        $images = [];
        foreach ($allImages as $imagePath) {
            try {
                [$width, $height] = $this->image->make($imagePath)->getRealSize();
                $images[] = [
                    'thumbnailURL' => $this->image->make($imagePath)
                        ->crop($cropAlgorithm, $imageWidth, $imageHeight)
                        ->url(),
                    'largeURL' => $this->image->make($imagePath)->resize($imageFullWidth, $imageFullHeight)->url(),
                    'width' => $width,
                    'height' => $height,
                    'alt' => Str::htmlEntityEncode($product_info['name']),
                ];
            } catch (Exception $e) {
                $this->logger->error($e->getMessage(), ['exception' => $e]);
            }
        }

        return $images;
    }
}
