<?php declare(strict_types=1);

namespace Newland\Toubiz\Sync\Neos\ErrorHandling;

use Neos\Flow\Annotations as Flow;
use Neos\Flow\Aop\JoinPointInterface;
use Neos\Flow\Log\ThrowableStorageInterface;
use Newland\Toubiz\Api\Exception\StringCleaningException;
use Newland\Toubiz\Sync\Neos\Domain\Repository\AbstractRepository;
use Newland\Toubiz\Sync\Neos\Importer\AbstractImporter;

/**
 * @Flow\Aspect()
 */
class LogImportErrorAndCarryOnAspect
{

    /**
     * @var ThrowableStorageInterface
     * @Flow\Inject()
     */
    protected $exceptionLogger;

    /**
     * @var ValidationService
     * @Flow\Inject()
     */
    protected $validationService;

    /**
     * Validates the passed entity before saving it in order to catch invalid data
     * before it is sent to the database.
     *
     * This will throw an `ValidationException` whenever data is invalid.
     *
     * @Flow\Before("method(Newland\Toubiz\Sync\Neos\Domain\Repository\.*Repository->(add|update)())")
     * @throws ValidationException
     */
    public function validateEntitiesBeforePersisting(JoinPointInterface $joinPoint): void
    {
        /** @var AbstractRepository $repository */
        $repository = $joinPoint->getProxy();
        $arguments = $joinPoint->getMethodArguments();
        $entity = $arguments[array_keys($arguments)[0]];

        $result = $this->validationService->validateEntity($entity, $repository->getEntityClassName());
        if ($result->hasErrors()) {
            throw ValidationException::forResult($result, $entity);
        }
    }

    /**
     * Logs validation errors that occur during imports.
     * If a `ValidationException` is thrown in the `import` method then this exception
     * is logged & saved and the method will return null.
     *
     * NOTE: The 'returning null on error' part means that all type annotations on `import`
     *       methods must be nullable.
     *
     * @Flow\Around("method(Newland\Toubiz\Sync\Neos\Importer\.*Importer->import())")
     */
    public function logValidationErrorsWhenImporting(JoinPointInterface $joinPoint)
    {
        /** @var AbstractImporter $importer */
        $importer = $joinPoint->getProxy();

        try {
            return $joinPoint->getAdviceChain()->proceed($joinPoint);
        } catch (ValidationException $e) {
            $this->logException($importer, $e);
            return null;
        } catch (StringCleaningException $e) {
            $this->logException($importer, $e);
            return null;
        }
    }

    private function logException(AbstractImporter $importer, \Throwable $throwable): void
    {
        $data = [];

        if ($throwable instanceof ValidationException) {
            $data['validationMessages'] = $throwable->getValidationMessages();
        }

        $importer->error($this->exceptionLogger->logThrowable($throwable, $data), $data);
    }
}
