<?php

declare(strict_types=1);

namespace KupShop\GraphQLBundle\ApiAdmin\Util;

use KupShop\GraphQLBundle\ApiAdmin\Types\Parameters;
use KupShop\GraphQLBundle\ApiAdmin\Types\Return\Input\ReturnCreateInput;
use KupShop\GraphQLBundle\ApiAdmin\Types\Return\Input\ReturnFilterInput;
use KupShop\GraphQLBundle\ApiAdmin\Types\Return\Input\ReturnItemInput;
use KupShop\GraphQLBundle\ApiAdmin\Types\Return\Input\ReturnUpdateInput;
use KupShop\GraphQLBundle\ApiAdmin\Types\Return\Response\ReturnMutateResponse;
use KupShop\GraphQLBundle\ApiAdmin\Types\Return\ReturnCollection;
use KupShop\GraphQLBundle\ApiAdmin\Types\Return\ReturnDto;
use KupShop\GraphQLBundle\ApiShared\ApiUtil;
use KupShop\GraphQLBundle\ApiShared\Util\ApiValidator;
use KupShop\GraphQLBundle\Exception\GraphQLNotFoundException;
use KupShop\GraphQLBundle\Exception\GraphQLValidationException;
use KupShop\KupShopBundle\Util\Database\QueryHint;
use KupShop\KupShopBundle\Util\Functional\Mapping;
use KupShop\KupShopBundle\Util\ObjectUtil;
use KupShop\ReturnsBundle\ReturnList\ReturnList;
use KupShop\ReturnsBundle\Util\ReturnsUtil;
use Query\Operator;

readonly class ReturnUtil
{
    public function __construct(
        private ApiValidator $validator,
        private ?ReturnList $returnList,
        private ?ReturnsUtil $returnsUtil,
    ) {
    }

    public function getReturn(int $id): ReturnDto
    {
        $returns = $this->getReturnList()
            ->andSpec(Operator::equals(['r.id' => $id]))
            ->getCollection();

        if (!($return = $returns->current())) {
            throw new GraphQLNotFoundException('Return not found');
        }

        return new ReturnDto($return, $returns);
    }

    public function getReturns(int $offset, int $limit, ?Parameters $sort, ?ReturnFilterInput $filter): ReturnCollection
    {
        $returnList = $this->getReturnList()
            ->andSpec(ApiUtil::getLimitSpec($offset, $limit))
            ->andSpec(ApiUtil::getSortSpec($sort, 'r'));

        if ($filter?->id) {
            $returnList->andSpec(Operator::inIntArray($filter->id, 'r.id'));
        }

        if ($filter?->code) {
            $returnList->andSpec(Operator::inStringArray($filter->code, 'r.code'));
        }

        if ($filter?->status) {
            $returnList->andSpec(Operator::inIntArray($filter->status, 'r.status'));
        }

        if ($filter?->dateCreated) {
            $returnList->andSpec(ApiUtil::getDateTimeFilter($filter->dateCreated, 'r.date_created'));
        }

        $returns = $returnList->getCollection($totalCount);

        return (new ReturnCollection($returns))
            ->setOffset($offset)
            ->setLimit(ApiUtil::getLimit($limit))
            ->setItemsTotalCount($totalCount);
    }

    public function createReturn(ReturnCreateInput $input): ReturnMutateResponse
    {
        $this->validateReturnCreateInput($input);

        $items = Mapping::mapKeys(
            $input->items,
            fn ($k, ReturnItemInput $x) => [
                $x->orderItemId,
                [
                    'pieces' => $x->pieces,
                    'return_reason' => $x->returnReasonId,
                ],
            ]
        );

        $replacements = [];
        foreach ($input->replacements ?? [] as $replacement) {
            $replacements[] = [
                'id_product' => $replacement->productId,
                'id_variation' => $replacement->variationId,
                'pieces' => $replacement->pieces,
                'price' => $replacement->price,
            ];
        }

        $returnId = sqlGetConnection()->transactional(function () use ($input, $items, $replacements) {
            $returnId = $this->returnsUtil->createReturn(
                bankAccount: $input->bankAccount,
                items: $items,
                replacements: $replacements,
                userNote: $input->noteUser,
            );

            $this->returnsUtil->recalculate($returnId);

            return $returnId;
        });

        return new ReturnMutateResponse(
            result: true,
            returnLoader: fn () => QueryHint::withRouteToMaster(fn () => $this->getReturn($returnId)),
        );
    }

    public function updateReturn(ReturnUpdateInput $input): ReturnMutateResponse
    {
        if (!isset($input->id) || !($return = $this->returnsUtil->getReturn($input->id))) {
            throw new GraphQLNotFoundException(message: 'Return was not found!');
        }

        if (ObjectUtil::isPropertyInitialized($input, 'status') && $input->status) {
            if ($input->status->id && !($this->returnsUtil->getStatuses()[$input->status->id] ?? false)) {
                throw new GraphQLValidationException(
                    message: sprintf('Status with ID "%s" was not found!', $input->status->id),
                    extensions: ['field' => 'status.id']
                );
            }

            if ($input->status->comment) {
                $this->returnsUtil->logHistory($input->id, $input->status->comment, status: $input->status->id ?? $return->getStatus());
            }

            $this->returnsUtil->changeStatus(
                entityId: $input->id,
                statusId: $input->status->id,
                sendMail: $input->status->sendEmail,
            );
        }

        return new ReturnMutateResponse(
            result: true,
            returnLoader: fn () => QueryHint::withRouteToMaster(fn () => $this->getReturn($input->id)),
        );
    }

    private function validateReturnCreateInput(ReturnCreateInput $input): void
    {
        if (empty($input->items)) {
            throw new GraphQLValidationException(
                message: 'Items cannot be empty!',
                extensions: ['field' => 'items'],
            );
        }

        $foundItems = sqlQueryBuilder()
            ->select('id, 1')
            ->from('order_items')
            ->where(Operator::inIntArray(array_map(fn ($x) => $x->orderItemId, $input->items), 'id'))
            ->execute()->fetchAllKeyValue();

        $returnReasons = $this->returnsUtil->getReasons();
        foreach ($input->items as $key => $item) {
            if (!isset($foundItems[$item->orderItemId])) {
                throw new GraphQLValidationException(
                    message: sprintf('Order item ID "%s" was not found!', $item->orderItemId),
                    extensions: ['field' => 'items['.$key.'].orderItemId']
                );
            }

            if (($returnReasons[$item->returnReasonId] ?? false) === false) {
                throw new GraphQLValidationException(
                    message: sprintf('Specified return reason "%s" does not exists!', $item->returnReasonId),
                    extensions: ['field' => 'items['.$key.'].returnReasonId']
                );
            }
        }

        foreach ($input->replacements ?? [] as $item) {
            $this->validator->productExists($item->productId, $item->variationId);
        }
    }

    private function getReturnList(): ReturnList
    {
        return clone $this->returnList;
    }
}
