<?php

declare(strict_types=1);

namespace KupShop\GraphQLBundle\ApiAdmin\Util;

use KupShop\GraphQLBundle\ApiAdmin\Types\Seller\Input\SellerInput;
use KupShop\GraphQLBundle\ApiAdmin\Types\Seller\Response\SellerMutateResponse;
use KupShop\GraphQLBundle\ApiShared\ApiUtil;
use KupShop\GraphQLBundle\ApiShared\Types\Seller\Seller;
use KupShop\GraphQLBundle\ApiShared\Types\Seller\SellerCollection;
use KupShop\GraphQLBundle\ApiShared\Util\PhotoUtil;
use KupShop\GraphQLBundle\Exception\GraphQLNotFoundException;
use KupShop\GraphQLBundle\Exception\GraphQLValidationException;
use KupShop\KupShopBundle\Util\ObjectUtil;
use KupShop\SellerBundle\SellerList\SellerList;
use KupShop\StoresBundle\Utils\StoresInStore;
use Query\Operator;

class SellerUtil
{
    public function __construct(
        private PhotoUtil $photoUtil,
        private ?SellerList $sellerList,
        private ?\KupShop\SellerBundle\Utils\SellerUtil $sellerUtil,
        private ?StoresInStore $storesInStore,
    ) {
    }

    public function getSellers(int $offset = 0, int $limit = 100): SellerCollection
    {
        $sellerList = $this->getSellerList()
            ->limit(ApiUtil::getLimit($limit), $offset);

        $sellers = $sellerList->getSellers($totalCount);

        $sellers->fetchSellersPhotos();
        $sellers->fetchSellersBlocks();

        return (new SellerCollection(ApiUtil::wrapItems($sellers, Seller::class)))
            ->setItemsTotalCount($totalCount)
            ->setLimit($limit)
            ->setOffset($offset);
    }

    public function getSeller(int $id): Seller
    {
        $sellers = $this->getSellerList()
            ->andSpec(Operator::equals(['se.id' => $id]))
            ->getSellers();

        if ($seller = $sellers->first()) {
            return new Seller($seller);
        }

        throw new GraphQLNotFoundException('Seller not found');
    }

    public function createOrUpdateSeller(SellerInput $input): SellerMutateResponse
    {
        $foundSeller = null;
        if (isset($input->id)) {
            $foundSeller = $this->sellerUtil->getSeller($input->id);
        }

        // pokud vytvarim prodejnu a nemam nazev, tak hodim chybu
        if (!$foundSeller && !isset($input->name)) {
            throw new GraphQLValidationException('Parameter "name" is required when creating seller!');
        }

        // overim, ze existuje sklad
        if (ObjectUtil::isPropertyInitialized($input, 'storeId') && $input->storeId !== null) {
            if (!($this->storesInStore->getStores()[$input->storeId] ?? false)) {
                throw new GraphQLValidationException(sprintf('Store ID "%s" does not exists!', $input->storeId));
            }
        }

        // zvaliduju JSON data
        if (isset($input->data)) {
            try {
                json_decode_strict($input->data, true);
            } catch (\JsonException $e) {
                throw new GraphQLValidationException('Parameter "data" has invalid format!');
            }
        }

        $data = $this->prepareSellerData($input);

        $sellerId = sqlGetConnection()->transactional(function () use ($input, $data, $foundSeller) {
            if ($foundSeller) {
                $qb = sqlQueryBuilder()
                    ->update('sellers')
                    ->directValues($data)
                    ->where(Operator::equals(['id' => $foundSeller['id']]));

                if (isset($input->latitude) && isset($input->longitude)) {
                    $qb->set('position', 'POINT(:x, :y)')
                        ->addParameters(['x' => $input->latitude, 'y' => $input->longitude]);
                }

                $qb->execute();

                return (int) $foundSeller['id'];
            }

            $qb = sqlQueryBuilder()
                ->insert('sellers')
                ->directValues($data);

            if (isset($input->latitude) && isset($input->longitude)) {
                $qb->setValue('position', 'POINT(:x, :y)')
                    ->addParameters(['x' => $input->latitude, 'y' => $input->longitude]);
            }

            $qb->execute();

            if (!empty($data['id'])) {
                return (int) $data['id'];
            }

            return (int) sqlInsertId();
        });

        if (ObjectUtil::isPropertyInitialized($input, 'deletePhotos') && $input->deletePhotos) {
            sqlQueryBuilder()
                ->delete('photos_sellers_relation')
                ->where(Operator::equals(['id_seller' => $sellerId]))
                ->execute();
        }

        if (ObjectUtil::isPropertyInitialized($input, 'photos')) {
            $this->photoUtil->updateObjectPhotos('seller', $sellerId, $input->photos ?: []);
        }

        if (ObjectUtil::isPropertyInitialized($input, 'flags') && $input->flags) {
            $flagsQb = sqlQueryBuilder()
                ->update('sellers')
                ->where(Operator::equals(['id' => $sellerId]));

            if (ObjectUtil::isPropertyInitialized($input->flags, 'delete') && $input->flags->delete) {
                $flagsQb->directValues(['flags' => '']);
            }

            if (ObjectUtil::isPropertyInitialized($input->flags, 'id')) {
                foreach ($input->flags->id ?? [] as $key => $flag) {
                    $flagsQb->set('flags', "ADD_TO_SET(:flag_{$key}, flags)")
                        ->setParameter("flag_{$key}", $flag);
                }
            }

            $flagsQb->execute();
        }

        $seller = sqlGetConnection()->transactional(fn () => $this->sellerUtil->getSellers(true)[$sellerId]);

        return new SellerMutateResponse(true, new Seller($seller));
    }

    protected function prepareSellerData(SellerInput $input, ?array $seller = null): array
    {
        $data = [];
        foreach (ObjectUtil::getProperties($input) as $property) {
            if (ObjectUtil::isPropertyInitialized($input, $property)) {
                $data[$property] = $input->{$property};
            }
        }

        // data fixtures
        unset($data['name'], $data['description'], $data['storeId'], $data['zip'], $data['latitude'], $data['longitude'], $data['visible'], $data['photos'], $data['deletePhotos'], $data['content'], $data['flags']);

        $data['figure'] = $input->visible ? 'Y' : 'N';

        if (ObjectUtil::isPropertyInitialized($input, 'description')) {
            $data['descr'] = $input->description;
        }

        if (ObjectUtil::isPropertyInitialized($input, 'storeId')) {
            $data['id_store'] = $input->storeId;
        }

        if (ObjectUtil::isPropertyInitialized($input, 'zip')) {
            $data['psc'] = $input->zip;
        }

        if (isset($input->name)) {
            $data['title'] = $input->name;
        }

        if ($seller && !empty($seller['data']) && isset($input->data)) {
            $data['data'] = json_encode(array_replace_recursive($seller['data'], json_decode($input->data ?: '', true) ?? []));
        }

        return $data;
    }

    private function getDownloader(): \Downloader
    {
        static $downloader;

        if (!$downloader) {
            $downloader = new \Downloader();
            $downloader->setMethod('curl');
        }

        return $downloader;
    }

    private function getSellerList(): SellerList
    {
        return clone $this->sellerList;
    }
}
