<?php

namespace KupShop\POSBundle\Util;

use KupShop\GraphQLBundle\ApiAdmin\Types\Collection\ProductCollection;
use KupShop\GraphQLBundle\ApiPos\Types\Collection\PosCollection;
use KupShop\GraphQLBundle\ApiPos\Types\Collection\PosIframeCollection;
use KupShop\GraphQLBundle\ApiPos\Types\Collection\PosModulesCollection;
use KupShop\GraphQLBundle\ApiPos\Types\Collection\PosOrderHistoryCollection;
use KupShop\GraphQLBundle\ApiPos\Types\Collection\PosOrderStatusCollection;
use KupShop\GraphQLBundle\ApiPos\Types\Collection\PosPermissionCollection;
use KupShop\GraphQLBundle\ApiPos\Types\Collection\PosVatCollection;
use KupShop\GraphQLBundle\ApiPos\Types\Order\PosOrderHistoryItem;
use KupShop\GraphQLBundle\ApiPos\Types\Order\PosOrderStatus;
use KupShop\GraphQLBundle\ApiPos\Types\Pos;
use KupShop\GraphQLBundle\ApiPos\Types\PosAdditionalData;
use KupShop\GraphQLBundle\ApiPos\Types\PosDeliverySetting;
use KupShop\GraphQLBundle\ApiPos\Types\PosIframe;
use KupShop\GraphQLBundle\ApiPos\Types\PosTerminal;
use KupShop\GraphQLBundle\ApiPos\Types\PosVat;
use KupShop\GraphQLBundle\ApiPos\Types\Stats\PosPaymentStats;
use KupShop\GraphQLBundle\ApiPos\Types\Stats\PosStats;
use KupShop\GraphQLBundle\ApiPos\Types\Stats\PosStatVatPrice;
use KupShop\GraphQLBundle\ApiPos\Types\Stats\PosSummaryStats;
use KupShop\GraphQLBundle\ApiShared\Types\DateInterval;
use KupShop\KupShopBundle\Context\CountryContext;
use KupShop\KupShopBundle\Context\CurrencyContext;
use KupShop\KupShopBundle\Context\PosContext;
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
use KupShop\KupShopBundle\Util\Contexts;
use KupShop\POSBundle\Email\InvoiceEmail;
use Query\Operator;
use Symfony\Component\DependencyInjection\ServiceLocator;

class PosUtil
{
    private const ORDER_HISTORY_PER_PAGE = 10;

    /** @var CurrencyContext */
    private $currencyContext;

    private CountryContext $countryContext;

    /** @var PosTabInterface[] */
    private ServiceLocator $serviceLocator;

    public function __construct(ServiceLocator $serviceLocator)
    {
        $this->serviceLocator = $serviceLocator;
        $this->currencyContext = ServiceContainer::getService(CurrencyContext::class);
        $this->countryContext = ServiceContainer::getService(CountryContext::class);
    }

    public function getPointsOfSale(): PosCollection
    {
        $qb = sqlQueryBuilder()
            ->select('id, name')
            ->from('pos')
            ->execute()
            ->fetchAll();

        $posCollection = [];

        foreach ($qb as $item) {
            $posCollection[] = new Pos(
                intval($item['id']),
                $item['name']
            );
        }

        return new PosCollection($posCollection);
    }

    public function getFastAccessProducts(int $idPos): ProductCollection
    {
        $fastAccessIds = sqlQueryBuilder()
            ->select("JSON_EXTRACT(data, '$.fast_access_products') as products")
            ->from('pos')
            ->where('id = :id_pos')
            ->setParameter('id_pos', $idPos)
            ->execute()
            ->fetch();
        $fastAccessProducts = new \KupShop\CatalogBundle\ProductList\ProductCollection();
        foreach (json_decode($fastAccessIds['products']) as $id) {
            $product = new \Product();
            $product->createFromDB($id);
            $product->fetchImages(1);
            $fastAccessProducts->set($id, $product);
        }

        return new ProductCollection($fastAccessProducts);
    }

    public function getOrderHistory(int $idPos, int $page): PosOrderHistoryCollection
    {
        // op.method konstanty jsou z class.Payment.php
        $qb = sqlQueryBuilder()
            ->select('o.order_no, o.date_created, u.name, u.surname, u.email, o.id, SUM(op.price) as paid, o.total_price, u.id as user_id, o.status, o.status_storno')
            ->addSelect("
                GROUP_CONCAT(
                    CASE
                        WHEN op.method = 1 THEN 'Hotově'
                        WHEN op.method = 2 THEN 'Kartou'
                        WHEN op.method = 3 THEN 'Na fakturu'
                        ELSE 'Neznámý způsob'
                        END
                    SEPARATOR ', '
                ) AS payment_methods
            ")
            ->from('orders', 'o')
            ->leftJoin('o', 'users', 'u', 'o.id_user = u.id')
            ->leftJoin('o', 'order_payments', 'op', 'o.id = op.id_order AND op.status = :payment_status_finished')
            ->setParameter('payment_status_finished', \Payment::STATUS_FINISHED)
            ->where('o.pos = :id_pos')
            ->groupBy('o.order_no, o.date_created')
            ->orderBy('o.date_created DESC, o.order_no')
            ->setParameter('id_pos', $idPos)
            ->setMaxResults(10)
            ->setFirstResult(($page - 1) * static::ORDER_HISTORY_PER_PAGE);

        if (findModule(\Modules::CURRENCIES)) {
            $qb->leftJoin('o', 'currencies', 'c', 'o.currency=c.id')
                ->addSelect('c.symbol as currency_symbol');
        } else {
            $qb->addSelect("'Kč' as currency_symbol");
        }

        $historyCollection = [];
        foreach ($qb->execute()->fetchAll() as $order) {
            $historyCollection[] = new PosOrderHistoryItem($order);
        }

        return new PosOrderHistoryCollection(
            items: $historyCollection
        );
    }

    public function getPosStats(int $idPos, ?DateInterval $interval = null): PosStats
    {
        $posEntity = new PosEntity();
        $posEntity->createFromDB($idPos)->activateContexts();

        $posAppStats = new PosAppStats();
        $posAppStats->setPosEntity($posEntity);
        $posAppStats->setDatetimeInterval($interval);
        $posAppStats->fetchStats();

        return new PosStats(
            $this->loadStatsForMethod($posAppStats->getStats(), \Payment::METHOD_CASH),
            $this->loadStatsForMethod($posAppStats->getStats(), \Payment::METHOD_CARD),
            $this->loadStatsForMethod($posAppStats->getStats(), \Payment::METHOD_INVOICE),
            $posAppStats->getPaidOfTheInvoiceCash(),
            $posAppStats->getPaidOfTheInvoiceCard(),
            new PosSummaryStats(
                $posAppStats->getCashInsertion(),
                $posAppStats->getCashSelection(),
                $posAppStats->getCompensation(),
                $posAppStats->getRounding(),
                $posAppStats->getDiscounts(),
                $posAppStats->getSales(),
                $posAppStats->getActualCashInPos(),
                $posAppStats->getNonHandledOrdersCount(),
                $posAppStats->getNonHandledOrdersPriceSum(),
            )
        );
    }

    public function sendInvoiceByEmail($idOrder): int
    {
        $emailService = ServiceContainer::getService(InvoiceEmail::class);
        $order = new \Order();
        $order->createFromDB($idOrder);

        $emailService->setOrder($order);
        $message = $emailService->getEmail();

        // Send it
        $emailService->sendEmail($message);

        return 0;
    }

    private function loadStatsForMethod($stats, $method): PosPaymentStats
    {
        $vats = [];
        foreach ($stats[$method] as $vat => $price) {
            $vats[] = new PosStatVatPrice($vat, $price);
        }

        return new PosPaymentStats($vats);
    }

    public function loadAdditionalData(int $idPos): PosAdditionalData
    {
        if (!$idPos) {
            throw new \Exception('Není vybrána žádná pokladna');
        }

        Contexts::get(PosContext::class)->activate($idPos);
        $posEntity = Contexts::get(PosContext::class)->getActive();

        // Loads modules to pos
        $modules = $posEntity->getCheckedModules();

        // Load permissions to pos
        $permissions = $posEntity->getCheckedPermissions();

        $delivery_types = sqlQueryBuilder()
            ->select('CONCAT(cash_dtp.name, " - ", cash_dtd.name) as cash')
            ->addSelect('CONCAT(card_dtp.name, " - ", card_dtd.name) as card')
            ->addSelect('CONCAT(invoice_dtp.name, " - ", invoice_dtd.name) as invoice')
            ->addSelect('custom_dtp.name as custom')
            ->from('pos', 'pos')
            ->leftJoin('pos', 'delivery_type', 'cash_dt', 'cash_dt.id=pos.cash_delivery_type')
            ->leftJoin('cash_dt', 'delivery_type_delivery', 'cash_dtd', 'cash_dtd.id=cash_dt.id_delivery')
            ->leftJoin('cash_dt', 'delivery_type_payment', 'cash_dtp', 'cash_dtp.id=cash_dt.id_payment')
            ->leftJoin('pos', 'delivery_type', 'card_dt', 'card_dt.id=pos.card_delivery_type')
            ->leftJoin('card_dt', 'delivery_type_delivery', 'card_dtd', 'card_dtd.id=card_dt.id_delivery')
            ->leftJoin('card_dt', 'delivery_type_payment', 'card_dtp', 'card_dtp.id=card_dt.id_payment')
            ->leftJoin('pos', 'delivery_type', 'invoice_dt', 'invoice_dt.id=pos.invoice_delivery_type')
            ->leftJoin('invoice_dt', 'delivery_type_delivery', 'invoice_dtd', 'invoice_dtd.id=invoice_dt.id_delivery')
            ->leftJoin('invoice_dt', 'delivery_type_payment', 'invoice_dtp', 'invoice_dtp.id=invoice_dt.id_payment')
            ->leftJoin('pos', 'delivery_type', 'custom_dt', 'custom_dt.id=pos.custom_delivery_type')
            ->leftJoin('custom_dt', 'delivery_type_payment', 'custom_dtp', 'custom_dtp.id=custom_dt.id_payment')
            ->where(Operator::equals(['pos.id' => $idPos]))
            ->execute()
            ->fetchAssociative();

        $statuses = [];
        foreach (getOrderStatuses() as $key => $orderStatus) {
            $statuses[] = new PosOrderStatus($key, $orderStatus['name'] ?? '', getStatuses('handled')[0] == $key ? 'handled' : null);
        }

        $iframes = [];
        foreach ($this->serviceLocator->getProvidedServices() as $className) {
            if ($this->serviceLocator->has($className)) {
                /** @var PosTabInterface $service */
                $service = $this->serviceLocator->get($className);
                if ($service->isAllowed()) {
                    $iframes[] = new PosIframe($service->getId(), $service->getTitle(), $service->getUrl());
                }
            }
        }

        return new PosAdditionalData(
            $posEntity->getName(),
            $this->loadVats(),
            $this->currencyContext->getActive(),
            $this->countryContext->getActive(),
            new PosModulesCollection($modules),
            new PosDeliverySetting($delivery_types),
            new PosOrderStatusCollection($statuses),
            new PosIframeCollection($iframes),
            new PosPermissionCollection($permissions),
            new PosTerminal($posEntity->getTerminalData()),
            $posEntity->getStores(),
            $posEntity->getWarehousePositions()
        );
    }

    private function loadVats(): PosVatCollection
    {
        $qb = sqlQueryBuilder()
            ->select('descr, vat, is_default')
            ->from('vats');

        if (findModule(\Modules::OSS_VATS)) {
            $qb->addSelect('id_country');
        } else {
            $qb->addSelect("'{$this->countryContext->getActive()->getId()}' AS id_country");
        }

        $vats = [];
        foreach ($qb->execute()->fetchAll() as $vat) {
            $vats[] = new PosVat($vat['descr'], $vat['vat'], $vat['is_default'], $vat['id_country']);
        }

        return new PosVatCollection($vats);
    }

    public function getPosDeliveryType($idPos, $methodPayment)
    {
        $paymentMethod = strtolower($methodPayment);

        return sqlQueryBuilder()
            ->select('dt.id, CONCAT(dtp.name, " - ", dtd.name) as name, dtd.class as delivery_class, dtp.class as payment_class, dtp.id as payment_id')
            ->from('pos', 'pos')
            ->leftJoin('pos', 'delivery_type', 'dt', "dt.id=pos.{$paymentMethod}_delivery_type")
            ->leftJoin('dt', 'delivery_type_delivery', 'dtd', 'dtd.id=dt.id_delivery')
            ->leftJoin('dt', 'delivery_type_payment', 'dtp', 'dtp.id=dt.id_payment')
            ->where(Operator::equals(['pos.id' => $idPos]))
            ->execute()
            ->fetch();
    }

    public static function getHandledOrderStatus(): int
    {
        $settingsStatus = \Settings::getDefault()->loadValue('pos')['handle_status'] ?? -1;
        if ($settingsStatus >= 0) {
            return (int) $settingsStatus;
        }

        return (int) getStatuses('handled')[0];
    }
}
