<?php

namespace KupShop\GraphQLBundle\ApiAdmin\Types\User;

use KupShop\BonusProgramBundle\Utils\BonusProvider;
use KupShop\GraphQLBundle\ApiAdmin\Annotation\Module;
use KupShop\GraphQLBundle\ApiAdmin\Types\OrderDiscount\DiscountCoupon;
use KupShop\GraphQLBundle\ApiAdmin\Util\CouponUtil;
use KupShop\GraphQLBundle\ApiShared\ApiUtil;
use KupShop\GraphQLBundle\ApiShared\Types\Enums\GenderEnum;
use KupShop\GraphQLBundle\ApiShared\Types\PriceLevel\PriceLevel;
use KupShop\GraphQLBundle\ApiShared\Types\PriceList\PriceList;
use KupShop\GraphQLBundle\ApiShared\Types\User\Address;
use KupShop\GraphQLBundle\ApiShared\Types\User\UserAddress;
use KupShop\GraphQLBundle\ApiShared\TypesData\UserData;
use KupShop\GraphQLBundle\Contracts\AbstractDataType;
use KupShop\KupShopBundle\Context\ContextManager;
use KupShop\KupShopBundle\Context\PriceLevelContext;
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
use KupShop\KupShopBundle\Util\Contexts;
use KupShop\PricelistBundle\Util\PriceListWorker;
use KupShop\UserAddressesBundle\Entity\DeliveryAddressEntity;
use KupShop\UserAddressesBundle\Util\UserAddressesUtil;
use Query\Operator;
use TheCodingMachine\GraphQLite\Annotations\Autowire;
use TheCodingMachine\GraphQLite\Annotations\Field;
use TheCodingMachine\GraphQLite\Annotations\Type;

/**
 * @Type()
 */
class User extends \KupShop\GraphQLBundle\ApiShared\Types\User\User
{
    /**
     * Fakturační adresa.
     *
     * @Field()
     */
    public function getInvoiceAddress(): Address
    {
        return new Address([
            'name' => $this->user['name'],
            'surname' => $this->user['surname'],
            'firm' => $this->user['firm'],
            'ico' => $this->user['ico'],
            'dic' => $this->user['dic'],
            'street' => $this->user['street'],
            'city' => $this->user['city'],
            'zip' => $this->user['zip'],
            'country' => $this->user['country'],
            'phone' => $this->user['phone'],
        ]);
    }

    /**
     * Dodací adresa.
     *
     * @Field()
     */
    public function getDeliveryAddress(): Address
    {
        return new Address([
            'name' => $this->user['delivery_name'],
            'surname' => $this->user['delivery_surname'],
            'firm' => $this->user['delivery_firm'],
            'street' => $this->user['delivery_street'],
            'city' => $this->user['delivery_city'],
            'zip' => $this->user['delivery_zip'],
            'country' => $this->user['delivery_country'],
            'phone' => $this->user['delivery_phone'],
        ]);
    }

    /**
     * Fakturační údaje.
     *
     * @Field()
     *
     * @deprecated use `invoiceAddress` field
     */
    public function getInvoiceDetails(): Address
    {
        return $this->getInvoiceAddress();
    }

    /**
     * Dodací údaje.
     *
     * @Field()
     *
     * @deprecated use `deliveryAddress` field
     */
    public function getDeliveryDetails(): Address
    {
        return $this->getDeliveryAddress();
    }

    /**
     * Pohlaví.
     *
     * @Field()
     */
    public function getGender(): ?GenderEnum
    {
        return !empty($this->user['gender']) ? GenderEnum::from($this->user['gender']) : null;
    }

    /**
     * Vrací, zda je uživatel aktivní.
     *
     * @Field()
     */
    public function getIsActive(): bool
    {
        return ($this->user['figure'] ?? 'N') === 'Y';
    }

    /**
     * Informace o odběru newsletteru.
     *
     * @Field()
     */
    public function getNewsletterInfo(): UserNewsletter
    {
        return new UserNewsletter($this->user);
    }

    /**
     * Datum registrace. Pokud je datum registrace `null`, tak to znamená, že uživatel není registrovaný, ale je např.
     * přihlášený pouze k newsletteru.
     *
     * @Field()
     */
    public function getDateRegistered(): ?\DateTimeInterface
    {
        return !empty($this->user['date_reg']) ? ApiUtil::prepareDateTimeFromDB($this->user['date_reg']) : null;
    }

    /**
     * Datum poslední změny.
     *
     * @Field()
     */
    public function getDateUpdated(): ?\DateTimeInterface
    {
        return !empty($this->user['date_updated']) ? ApiUtil::prepareDateTimeFromDB($this->user['date_updated']) : null;
    }

    /**
     * Datum posledního přihlášení.
     *
     * @Field()
     */
    public function getDateLogged(): ?\DateTimeInterface
    {
        return !empty($this->user['date_logged']) ? ApiUtil::prepareDateTimeFromDB($this->user['date_logged']) : null;
    }

    /**
     * Poznámka nastavená administrátorem u uživatele.
     *
     * @Field()
     */
    public function getNote(): ?string
    {
        return !empty($this->user['note']) ? $this->user['note'] : null;
    }

    /**
     * Ceník nastavený u uživatele.
     *
     * @Field()
     *
     * @Module("PRICELISTS")
     */
    public function getPriceList(): ?PriceList
    {
        if (!empty($this->user['id_pricelist'])) {
            $priceListWorker = ServiceContainer::getService(PriceListWorker::class);
            if ($priceList = $priceListWorker->getPriceList((int) $this->user['id_pricelist'])) {
                return new PriceList($priceList);
            }
        }

        return null;
    }

    /**
     * Počet bodů v bonusovém programu.
     *
     * @Field()
     *
     * @Module("BONUS_PROGRAM")
     */
    public function getBonusPoints(): ?int
    {
        if (findModule(\Modules::BONUS_PROGRAM)) {
            $bonusProvider = ServiceContainer::getService(BonusProvider::class);

            return $bonusProvider->getActivePointsAmount($this->user);
        }

        return null;
    }

    #[Field(prefetchMethod: 'prefetchDateLastOrder', description: 'Datum poslední objednávky.')]
    public function dateLastOrder(array $dateLastOrders): ?\DateTimeInterface
    {
        if ($date = ($dateLastOrders[$this->user->id] ?? null)) {
            return ApiUtil::prepareDateTimeFromDB($date);
        }

        return null;
    }

    #[Field(description: 'Cenová hladina.')]
    #[Module(\Modules::PRICE_LEVELS)]
    public function getPriceLevel(#[Autowire(ContextManager::class)] ContextManager $contextManager): ?PriceLevel
    {
        return $contextManager->activateUser($this->user, function () {
            $priceLevelContext = Contexts::get(PriceLevelContext::class);

            if ($priceLevel = $priceLevelContext->getActive()) {
                return new PriceLevel($priceLevel);
            }

            return null;
        });
    }

    #[Field(description: 'Datum narození.')]
    public function getBirthdate(): ?\DateTimeInterface
    {
        return ($birthdate = $this->user->birthdate ?? null)
            ? ApiUtil::prepareDateTimeFromDB($birthdate, 'Y-m-d')
            : null;
    }

    /**
     * @return UserGroup[]|null
     */
    #[Field(prefetchMethod: 'prefetchUserGroups', description: 'Zařazení do skupin')]
    public function getUserGroups(array $usersGroups): ?array
    {
        if ($groups = ($usersGroups[$this->user->id] ?? null)) {
            return ApiUtil::wrapItems($groups, UserGroup::class);
        }

        return null;
    }

    /**
     * @return DiscountCoupon[]
     */
    #[Field(prefetchMethod: 'prefetchUserCoupons', description: 'Poukazy uživatele.')]
    #[Module(\Modules::ORDER_DISCOUNT)]
    public function getCoupons(array $coupons): array
    {
        return ApiUtil::wrapItems(
            $coupons[$this->user->id] ?? [],
            DiscountCoupon::class,
        );
    }

    #[Field(outputType: 'UserData')]
    public function getData(): ?AbstractDataType
    {
        $className = ApiUtil::getDataTypeClassName(UserData::class);

        return new $className(
            $this->user->getCustomData()
        );
    }

    /**
     * Odběr newslettru.
     *
     * @Field()
     *
     * @deprecated Use `newsletterInfo` field
     */
    public function getNewsletter(): ?string
    {
        return $this->user['get_news'];
    }

    /**
     * Datum přihlášení k newsletteru.
     *
     * @Field()
     *
     * @deprecated Use `newsletterInfo` field
     */
    public function getSubscribeDate(): ?string
    {
        return $this->user['date_subscribe'];
    }

    /**
     * Datum odhlášení od newsletteru.
     *
     * @Field()
     *
     * @deprecated Use `newsletterInfo` field
     */
    public function getUnsubscribeDate(): ?string
    {
        return $this->user['date_unsubscribe'];
    }

    /**
     * Datum registrace.
     *
     * @Field()
     *
     * @deprecated Use `dateRegistered` field
     */
    public function getRegistrationDate(): ?string
    {
        return $this->user['date_reg'];
    }

    /**
     * Datum aktualizace.
     *
     * @Field()
     *
     * @deprecated Use `dateUpdated` field
     */
    public function getUpdateDate(): ?string
    {
        return $this->user['date_updated'];
    }

    /**
     * Datum posledního přihlášení.
     *
     * @Field()
     *
     * @deprecated Use `dateLogged` field
     */
    public function getLoggedDate(): ?string
    {
        return $this->user['date_logged'];
    }

    /**
     * Figure.
     *
     * @Field()
     *
     * @deprecated Use `isActive` field
     */
    public function getFigure(): ?string
    {
        return $this->user['figure'];
    }

    /** @return UserAddress[] */
    #[Field(prefetchMethod: 'prefetchUserDeliveryAddresses', description: 'Doručovací adresy')]
    #[Module(\Modules::USER_ADDRESSES)]
    public function getUserDeliveryAddresses(array $userAddresses): array
    {
        $result = [];
        /** @var DeliveryAddressEntity $address */
        foreach ($userAddresses[$this->user->id] ?? [] as $address) {
            $result[] = new UserAddress($address->getAddressArray());
        }

        return $result;
    }

    public function prefetchUserDeliveryAddresses(array $users): array
    {
        return $this->prefetchForUsers($users, function (array $userIds) {
            return $this->getUserAddressesUtil()->getDeliveryAddressesForUsers($userIds);
        });
    }

    public function prefetchDateLastOrder(array $users): array
    {
        return $this->prefetchForUsers($users, function (array $userIds) {
            return sqlQueryBuilder()
                ->select('u.id, MAX(o.date_created) as last_order_date')
                ->from('users', 'u')
                ->leftJoin('u', 'orders', 'o', 'u.id = o.id_user')
                ->where(Operator::inIntArray($userIds, 'u.id'))
                ->groupBy('u.id')
                ->execute()->fetchAllKeyValue();
        });
    }

    public function prefetchUserCoupons(array $users): array
    {
        return $this->prefetchForUsers($users, function (array $userIds) {
            $qb = ServiceContainer::getService(CouponUtil::class)->getCouponQueryBuilder()
                ->where(Operator::inIntArray($userIds, 'dc.id_user'));

            $data = [];
            foreach ($qb->execute() as $item) {
                $data[$item['id_user']][] = $item;
            }

            return $data;
        });
    }

    public function prefetchUserGroups(array $users): array
    {
        return $this->prefetchForUsers($users, function (array $userIds) {
            $usersGroupsRaw = sqlQueryBuilder()
                ->select('ugr.id_user, ug.*')
                ->from('users_groups_relations', 'ugr')
                ->leftJoin('ugr', 'users_groups', 'ug', 'ugr.id_group = ug.id')
                ->andWhere(Operator::inIntArray($userIds, 'ugr.id_user'))
                ->execute()->fetchAllAssociative();

            $usersGroups = [];
            foreach ($usersGroupsRaw as $item) {
                $userId = array_shift($item);
                $usersGroups[$userId][] = $item;
            }

            return $usersGroups;
        });
    }

    protected function prefetchForUsers(array $users, callable $prefetch): array
    {
        $userIds = array_map(fn (User $x) => $x->getId(), $users);
        if (empty($userIds)) {
            return [];
        }

        return $prefetch($userIds);
    }

    private function getUserAddressesUtil(): UserAddressesUtil
    {
        static $userAddressesUtil;

        return $userAddressesUtil ??= ServiceContainer::getService(UserAddressesUtil::class);
    }
}
