<?php declare(strict_types=1);

namespace Shop\Presentation\Controllers;

use Core\Helpers\Text;
use Diamond\Factory\Logger;
use Fi1a\Validation\Validator;
use League\Fractal\Manager;
use League\Fractal\Resource\Collection;
use Shop\Application\Catalog\CreateProduct\CreateProductCommand;
use Shop\Application\Catalog\UpdateProduct\PositionDto;
use Shop\Application\Catalog\UpdateProduct\UpdateProductCommand;
use Shop\Application\Catalog\UpdateProductPositions\UpdateProductPositionsCommand;
use Shop\Application\Catalog\UpdateProductPositions\UpdateProductPositionsException;
use Shop\Infrastructure\Models\Brand\Brand;
use Shop\Infrastructure\Models\Category\Category;
use Shop\Infrastructure\Repositories\Product\ProductRepository;
use Shop\Presentation\Transformers\ProductPositionTransformer;
use Shop\Presentation\Transformers\ProductTransformer;
use Shop\Presentation\Validators\ProductStockValidator;
use Shop\Presentation\Validators\ProductValidator;
use Shop\Infrastructure\Models\Product\Product;
use Shop\Infrastructure\Models\Product\ProductQuery;
use Shop\Infrastructure\Repositories\Brand\BrandCriteria;
use Shop\Infrastructure\Repositories\Brand\BrandRepository;
use Shop\Infrastructure\Repositories\Catalog\CatalogRepository;
use Shop\Infrastructure\Repositories\Product\ProductCriteria;
use Shop\Infrastructure\Repositories\ProductCategory\ProductCategoryCriteria;
use Shop\Infrastructure\Repositories\ProductCategory\ProductCategoryRepository;
use Shop\Infrastructure\Repositories\ProductGroup\ProductGroupCriteria;
use Shop\Infrastructure\Repositories\ProductGroup\ProductGroupRepository;
use spaceonfire\CommandBus\CommandBus;
use Symfony\Component\HttpFoundation\JsonResponse;

class ProductController extends RestController
{
    use ClientApiKeyTrait;

    public function __construct(protected CatalogRepository         $catalogRepository,
                                protected ProductCategoryRepository $productCategoryRepository,
                                protected ProductRepository         $productRepository,
                                protected CommandBus                $commandBus,
                                protected BrandRepository           $brandRepository,
                                protected ProductGroupRepository    $productGroupRepository,
                                protected Manager                   $manager)
    {
    }

    public function stocksAll(array $args): JsonResponse
    {
        $this->validateToken();

        $id = $args['id'] ?? null;

        if (!$this->catalogRepository->findById((int)$id)) {
            return $this->buildPageNotFound('catalog not found by id ' . $id);
        }

        $criteria = $this->buildProductCriteria();

        $items = $this->productRepository->findByCriteria($criteria);

        $data = $this->manager->createData(
            (new Collection($items, new ProductPositionTransformer()))->setMetaValue('total', $this->productRepository->count($criteria))
        );

        return $this->buildJsonResponse($data->toArray());
    }

    public function stocksUpdate(array $args): JsonResponse
    {
        $this->validateToken();

        $id = $args['id'] ?? null;

        if (!$this->catalogRepository->findById((int)$id)) {
            return $this->buildPageNotFound('catalog not found by id ' . $id);
        }

        $body = $this->parseBody();

        //валидация структуры
        $validator = new Validator();
        $validation = $validator->make(new ProductStockValidator($body), [], [], [], 'update');

        $result = $validation->validate();

        if (!$result->isSuccess()) {
            return $this->buildInvalidArgumentResponse($result->getErrors()->first()->getMessage());
        }

        $result = [];
        $errors = false;
        foreach ($body as $productAr) {

            $productId = $productAr['id'];

            /** @var Product $product */
            if (!$product = $this->productRepository->findById($productId)) {
                $result[$productId] = 'product not found by id ' . $productId;
                $errors = true;
                continue;
            }

            $positionCollection = $this->buildPositionsFromBody($productAr['positions']);

            try {
                $updateCommand = new UpdateProductPositionsCommand($product, $positionCollection);
                $this->commandBus->handle($updateCommand);
                $result[$productId] = 'success';
            } catch (UpdateProductPositionsException $e) {
                $result[$productId] = $e->getMessage();
                $errors = true;
            }
        }


        return $this->buildJsonResponse([
            'success' => $result,
            'error' => $errors
        ]);
    }

    public function productsAll(array $args): JsonResponse
    {
        $this->validateToken();

        $id = $args['id'] ?? null;

        if (!$this->catalogRepository->findById((int)$id)) {
            return $this->buildPageNotFound('catalog not found by id ' . $id);
        }

        $criteria = $this->buildProductCriteria();

        $items = $this->productRepository->findByCriteria($criteria);

        $data = $this->manager->createData(
            (new Collection($items, new ProductTransformer()))->setMetaValue('total', $this->productRepository->count($criteria))
        );

        return $this->buildJsonResponse($data->toArray());
    }

    private function buildProductCriteria(): ProductCriteria
    {
        $criteria = ProductCriteria::create()->setStrictWithPosition(false);//->setFilterByActive(true) не обязательно нужны активные для обновления

        $filter = $this->request->get('filter');

        if (isset($filter['article'])) {
            $criteria->setFilterByArticle($filter['article']);
        } elseif (isset($filter['articles'])) {
            $ids = ProductQuery::create()->select(['id'])->filterByArticle($filter['articles'])->find()->getData();
            $criteria->setFilterByIds($ids);
        }

        $limit = $this->request->get('limit');

        if ($limit > 100 || !$limit) {
            $limit = 100;
        }

        $criteria->setLimit($limit);

        return $criteria;
    }

    public function list(array $args): JsonResponse
    {
        $this->validateToken();

        $id = $args['id'] ?? null;

        $categoryId = $args['category_id'] ?? null;

        if (!$category = $this->productCategoryRepository->findByCriteria(
            ProductCategoryCriteria::create()->setFilterByCatalogId((int)$id)->setFilterById((int)$categoryId)
        )->current()) {
            return $this->buildPageNotFound('category not found by id ' . $categoryId . ' and catalog ' . $id);
        }

        $criteria = ProductCriteria::create()->setFilterByActive(true)->setFilterByCategoryId($category->getId());

        $filter = $this->request->get('filter');

        if (isset($filter['article'])) {
            $criteria->setFilterByArticle($filter['article']);
        }

        $items = $this->productRepository->findByCriteria($criteria);

        $data = $this->manager->createData(
            (new Collection($items, new ProductTransformer()))->setMetaValue('total', $this->productRepository->count($criteria))
        );

        return $this->buildJsonResponse($data->toArray());
    }

    /**
     * Создание товара с автосозданием раздела
     * @param array $args
     * @return JsonResponse
     */
    public function storeAll(array $args): JsonResponse
    {
        $this->validateToken();

        $logger = Logger::buildFileLogger(
            [\Monolog\Logger::DEBUG],
            'default',
            'product_controller_store',
            'Y_m_d'
        );

        $body = $this->parseBody();

        $category = $body['category'] ?? null;

        $id = $args['id'] ?? null;

        if (!$id) {
            return $this->buildPageNotFound('catalog is empty');
        }

        if (!$category) {
            return $this->buildPageNotFound('category attribute is empty');
        }

        $parentId = null;

        $separator = null;

        if (str_contains($category, '|')) {
            $categoryAr = explode('|', $category);
        } elseif (str_contains($category, '/')) {
            $categoryAr = explode('/', $category);
        } else {
            $categoryAr = [$category];
        }


        foreach ($categoryAr as $categoryName) {

            $categoryName = trim($categoryName);

            $criteria = ProductCategoryCriteria::create()->setFilterByCatalogId((int)$id)->setFilterByTitle($categoryName);

            if ($parentId) {
                $criteria->setFilterByParentIds([$parentId]);
            }

            $categoryModel = $this->productCategoryRepository->findByCriteria(
                $criteria
            )->current();

            if (!$categoryModel) {
                $categoryModel = (new Category())->setTitle($categoryName)->setAlias(Text::str2url($categoryName))->setParentId($parentId)->setCatalogId($id);
                $this->productCategoryRepository->save($categoryModel);
            }

            $parentId = $categoryModel->getId();
        }

        $args['category_id'] = $parentId;

        return $this->store($args);
    }


    public function store(array $args): JsonResponse
    {
        $this->validateToken();

        $logger = Logger::buildFileLogger(
            [\Monolog\Logger::DEBUG],
            'default',
            'product_controller_store',
            'Y_m_d'
        );

        $id = $args['id'] ?? null;

        if (!$id) {
            return $this->buildPageNotFound('catalog is empty');
        }

        $categoryId = $args['category_id'] ?? null;

        if (!$category = $this->productCategoryRepository->findByCriteria(
            ProductCategoryCriteria::create()->setFilterByCatalogId((int)$id)->setFilterById((int)$categoryId)
        )->current()) {
            return $this->buildPageNotFound('category not found by id ' . $categoryId);
        }

        $body = $this->parseBody();

        //валидация структуры
        $result = $this->validateRequest($body);

        if (is_a($result, JsonResponse::class)) {
            return $result;
        }

        $positionCollection = $this->buildPositionsFromBody($body['positions']);

        $command = new CreateProductCommand(
            name: $body['name'],
            sku: $body['article'],
            price: (float)$body['price'],
            brandId: (int)$result['brand_id'],
            categoryId: $category->getId(),
            images: $body['images'],
            description: $body['description'] ?? null,
            groupIds: !empty($result['group_ids']) ? (array)$result['group_ids'] : [],
            positionCollection: $positionCollection
        );

        $command->setLogger($logger);

        $this->commandBus->handle($command);


        return $this->buildJsonResponse([
            'sourceId' => $command->getProductEntity()->getId()
        ]);
    }

    private function buildPositionsFromBody(array $positions): array
    {
        $positionCollection = [];
        foreach ($positions as $position) {

            $remain = (int)$position['amount'];

            if (!$remain) {
                continue;
            }

            $positionCollection[] = new PositionDto(
                $position['description'],
                (float)$position['price'],
                $remain,
                $position['sizes'] ?? []
            );
        }
        return $positionCollection;
    }

    private function validateRequest(array $body, ?int $productId = null): mixed
    {


        $validator = new Validator();
        $validation = $validator->make(new ProductValidator($body), [], [], [], 'create');

        $result = $validation->validate();

        if (!$result->isSuccess()) {
            return $this->buildInvalidArgumentResponse($result->getErrors()->first()->getMessage());
        }

        if (!$brand = $this->brandRepository->findByCriteria(BrandCriteria::create()->setFilterByName($body['brand']))->current()) {
            $brand = (new Brand())->setName($body['brand'])->setAlias(Text::str2url($body['brand']));
            $this->brandRepository->save($brand);
            //return $this->buildInvalidArgumentResponse('Производитель c именем ' . $body['brand'] . ' не найден');
        }

        $groupIds = [];

        if (isset($body['groups'])) {
            foreach ($body['groups'] as $groupName) {
                $group = $this->productGroupRepository->findByCriteria(ProductGroupCriteria::create()->setFilterByName($groupName))->current();
                if (!$group) {
                    return $this->buildInvalidArgumentResponse('Группа с именем ' . $groupName . ' не найдена');
                }
                $groupIds[] = $group->getId();
            }
        }

        $findProducts = $this->productRepository->findByCriteria(ProductCriteria::create()->setFilterByArticle($body['article']));

        foreach ($findProducts as $findProduct) {
            if (!$productId
                || (!empty($productId) && $productId !== $findProduct->getId())) {
                return $this->buildInvalidArgumentResponse(
                    sprintf('Другой товар с таким артикулом `%s` и id `%s` уже есть в системе', $body['article'], $findProduct->getId())
                );
            }
        }

        return [
            'brand_id' => $brand->getId(),
            'group_ids' => $groupIds
        ];
    }

    public function update(array $args): JsonResponse
    {
        $this->validateToken();
        $logger = Logger::buildFileLogger(
            [\Monolog\Logger::DEBUG],
            'default',
            'product_controller_update',
            'Y_m_d'
        );
        $id = $args['id'] ?? null;
        $categoryId = $args['category_id'] ?? null;
        $productId = $args['product_id'] ?? null;

        if (!$category = $this->productCategoryRepository->findByCriteria(
            ProductCategoryCriteria::create()->setFilterByCatalogId((int)$id)->setFilterById((int)$categoryId)
        )->current()) {
            return $this->buildPageNotFound('category not found by id ' . $categoryId);
        }

        if (!$product = $this->productRepository->findById((int)$productId)) {
            return $this->buildPageNotFound('product not found by id ' . $productId);
        }

        $body = $this->parseBody();

        //валидация структуры
        $result = $this->validateRequest($body, (int)$productId);

        if (is_a($result, JsonResponse::class)) {
            return $result;
        }

        $positionCollection = $this->buildPositionsFromBody($body['positions']);

        $command = new UpdateProductCommand(
            product: $product,
            name: $body['name'],
            sku: $body['article'],
            price: (float)$body['price'],
            brandId: (int)$result['brand_id'],
            categoryId: (int)$category->getId(),
            images: $body['images'],
            description: $body['description'] ?? null,
            groupIds: !empty($result['group_ids']) ? (array)$result['group_ids'] : [],
            positionCollection: $positionCollection
        );
        $command->setLogger($logger);
        $this->commandBus->handle($command);

        return $this->buildSuccessUpdate();
    }


    public function delete(array $args): JsonResponse
    {
        $this->validateToken();

        $id = $args['id'] ?? null;
        $categoryId = $args['category_id'] ?? null;
        $productId = $args['product_id'] ?? null;

        if (!$category = $this->productCategoryRepository->findByCriteria(
            ProductCategoryCriteria::create()->setFilterByCatalogId((int)$id)->setFilterById((int)$categoryId)
        )->current()) {
            return $this->buildPageNotFound('category not found by id ' . $categoryId);
        }

        if (!$product = $this->productRepository->findById((int)$productId)) {
            return $this->buildPageNotFound('product not found by id ' . $productId);
        }

        $this->productRepository->delete($product);

        return $this->buildSuccessDelete();
    }
}