<?php

declare(strict_types=1);

namespace NoahVet\Reef\BusinessRule\Doctrine;

use NoahVet\Reef\Domain\Tool\StringTool;
use NoahVet\Reef\Entity\DisableInterface;
use NoahVet\Reef\Repository\BaseRepositoryInterface;

class EntityBusinessRule implements EntityBusinessRuleInterface
{
    /**
     * @template EntityType of object
     * @template ExceptionType of \Exception
     *
     * @param BaseRepositoryInterface<EntityType> $repository
     * @param class-string<ExceptionType>         $exceptionFQCN
     *
     * @return EntityType
     */
    public function entityShouldBeExist(
        array $params,
        BaseRepositoryInterface $repository,
        string $exceptionFQCN = \LogicException::class,
        ?string $exceptionMessage = null,
    ): object {
        $explodedClassName = \explode('\\', $repository->getClassName());
        $last = StringTool::fromPascalCaseToCamelCase(\end($explodedClassName));
        $resourceName = \str_replace('RepositoryInterface', '', $last);
        /** @var EntityType|null $existingEntity */
        $existingEntity = $repository->findOneBy($params);

        if (null === $existingEntity) {
            $this->throwExceptionWithMessage(
                $params,
                $resourceName,
                $exceptionFQCN,
                'cannot be found.',
                $exceptionMessage,
            );
        }
        \assert(null !== $existingEntity);

        return $existingEntity;
    }

    /**
     * @template EntityType of object
     * @template ExceptionType of \Exception
     *
     * @param BaseRepositoryInterface<EntityType> $repository
     * @param class-string<ExceptionType>         $exceptionFQCN
     *
     * @return EntityType
     */
    public function entityShouldBeExistById(
        string $id,
        BaseRepositoryInterface $repository,
        string $exceptionFQCN = \LogicException::class,
        ?string $exceptionMessage = null,
    ): object {
        return $this->entityShouldBeExist(
            ['id' => $id],
            $repository,
            $exceptionFQCN,
            $exceptionMessage,
        );
    }

    /**
     * @template EntityType of object
     * @template ExceptionType of \Exception
     *
     * @param BaseRepositoryInterface<EntityType> $repository
     * @param class-string<ExceptionType>         $exceptionFQCN
     */
    public function entityShouldNotBeExist(
        array $params,
        BaseRepositoryInterface $repository,
        string $exceptionFQCN = \LogicException::class,
        ?string $exceptionMessage = null,
    ): self {
        $explodedClassName = \explode('\\', $repository->getClassName());
        $last = StringTool::fromPascalCaseToCamelCase(\end($explodedClassName));
        $resourceName = \str_replace('RepositoryInterface', '', $last);
        /** @var EntityType|null $existingEntity */
        $existingEntity = $repository->findOneBy($params);

        if ($existingEntity) {
            $this->throwExceptionWithMessage(
                $params,
                $resourceName,
                $exceptionFQCN,
                'already exists.',
                $exceptionMessage,
            );
        }

        return $this;
    }

    /**
     * @template ExceptionType of \Exception
     *
     * @param class-string<ExceptionType> $exceptionFQCN
     *
     * @return $this
     *
     * @psalm-suppress UnsafeInstantiation
     */
    public function entityShouldNotBeDisabled(
        DisableInterface $entity,
        string $exceptionFQCN = \LogicException::class,
        ?string $exceptionMessage = null,
    ): self {
        if ($entity->isDisabled()) {
            throw new $exceptionFQCN($exceptionMessage ?? 'This entity is disabled.');
        }

        return $this;
    }

    /**
     * @template ExceptionType of \Exception
     *
     * @param array<string, mixed>        $params
     * @param class-string<ExceptionType> $exceptionFQCN
     *
     * @psalm-suppress UnsafeInstantiation
     */
    private function throwExceptionWithMessage(
        array $params,
        string $resourceName,
        string $exceptionFQCN,
        string $messageSuffix,
        ?string $exceptionMessage = null,
    ): void {
        if (null !== $exceptionMessage) {
            throw new $exceptionFQCN($exceptionMessage);
        }
        $message = \sprintf('The %s with', $resourceName);

        foreach ($params as $key => $value) {
            $message .= \sprintf(' `%s: "%s"`', $key, $value);
        }

        throw new $exceptionFQCN($message.' '.$messageSuffix);
    }
}
