<?php

namespace KupShop\GraphQLBundle\ApiAdmin\Util\Catalog;

use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use KupShop\GraphQLBundle\ApiAdmin\Types\Catalog\Product\Input\ProductUnitInput;
use KupShop\GraphQLBundle\ApiAdmin\Types\Catalog\Product\Response\ProductUnitMutateResponse;
use KupShop\GraphQLBundle\ApiAdmin\Types\Collection\ProductUnitCollection;
use KupShop\GraphQLBundle\ApiShared\ApiUtil;
use KupShop\GraphQLBundle\ApiShared\Types\Product\ProductUnit;
use KupShop\GraphQLBundle\Exception\GraphQLNotFoundException;
use KupShop\GraphQLBundle\Exception\GraphQLValidationException;
use KupShop\KupShopBundle\Util\Database\QueryHint;
use KupShop\KupShopBundle\Util\ObjectUtil;
use Query\Operator;

class ProductUnitUtil
{
    public function getProductUnit(int $id): ?ProductUnit
    {
        $productUnit = $this->getProductUnitQueryBuilder()
            ->where(Operator::equals(['id' => $id]))
            ->execute()->fetchAssociative();

        if (!$productUnit) {
            throw new GraphQLNotFoundException(
                sprintf('Product unit with ID "%s" was not found!', $id)
            );
        }

        return new ProductUnit($productUnit);
    }

    public function getProductUnits(): ProductUnitCollection
    {
        $qb = $this->getProductUnitQueryBuilder();

        return new ProductUnitCollection(ApiUtil::wrapItems($qb->execute(), ProductUnit::class));
    }

    public function createOrUpdateProductUnit(ProductUnitInput $input): ProductUnitMutateResponse
    {
        if (empty($input->id) && empty($input->nameAdmin)) {
            throw new GraphQLValidationException('Field "nameAdmin" is mandatory when creating product unit!');
        }

        $productUnitId = null;
        if (!empty($input->id)) {
            $productUnitId = $input->id;

            $unitExists = sqlQueryBuilder()
                ->select('id')
                ->from('products_units')
                ->where(Operator::equals(['id' => $productUnitId]))
                ->execute()->fetchOne();

            if (!$unitExists) {
                throw new GraphQLNotFoundException(
                    sprintf('Product unit with ID "%s" was not found!', $productUnitId)
                );
            }
        }

        $productUnitData = [];

        if (ObjectUtil::isPropertyInitialized($input, 'name')) {
            $productUnitData['short_name'] = $input->name;
        }

        if (ObjectUtil::isPropertyInitialized($input, 'nameAdmin')) {
            $productUnitData['short_name_admin'] = $input->nameAdmin;
        }

        if (ObjectUtil::isPropertyInitialized($input, 'longName')) {
            $productUnitData['long_name'] = $input->longName;
        }

        if (ObjectUtil::isPropertyInitialized($input, 'piecePrecision')) {
            $productUnitData['pieces_precision'] = $this->getPrecisionStep($input->piecePrecision);
        }

        if (ObjectUtil::isPropertyInitialized($input, 'recalculateTo')) {
            $productUnitData['recalculate_to'] = $input->recalculateTo;
        }

        if ($productUnitId) {
            if (!empty($productUnitData)) {
                sqlQueryBuilder()
                    ->update('products_units')
                    ->directValues($productUnitData)
                    ->where(Operator::equals(['id' => $input->id]))
                    ->execute();
            }
        } else {
            try {
                $productUnitId = sqlGetConnection()->transactional(function () use ($productUnitData) {
                    sqlQueryBuilder()
                        ->insert('products_units')
                        ->directValues($productUnitData)
                        ->execute();

                    return (int) sqlInsertId();
                });
            } catch (UniqueConstraintViolationException $e) {
                throw new GraphQLValidationException(
                    sprintf('Product unit with short name admin "%s" exists already!', $input->nameAdmin)
                );
            }
        }

        return new ProductUnitMutateResponse(
            true,
            QueryHint::withRouteToMaster(fn () => $this->getProductUnit($productUnitId))
        );
    }

    private function getProductUnitQueryBuilder()
    {
        return sqlQueryBuilder()
            ->select('pu.*')
            ->from('products_units', 'pu');
    }

    private function getPrecisionStep(int $precision): float
    {
        return match ($precision) {
            4 => 0.0001,
            3 => 0.001,
            2 => 0.01,
            1 => 0.1,
            0 => 0,
            default => throw new GraphQLValidationException(
                sprintf('Invalid product unit precision "%s". Only values 0-4 supported!', $precision)
            ),
        };
    }
}
