<?php

declare(strict_types=1);

namespace CpCreation\VitiCore\Versioning\Repository;

use CpCreation\VitiCore\Repository\BaseRepository;
use CpCreation\VitiCore\Versioning\Model\BusinessVersion;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;

final class BusinessVersionRepository extends BaseRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, BusinessVersion::class);
    }

    public function getNextVersion(string $objectClass, string $objectId, string $scope = 'default'): int
    {
        $lastVersion = $this->createQueryBuilder('version')
            ->select('MAX(version.version)')
            ->andWhere('version.objectClass = :objectClass')
            ->andWhere('version.objectId = :objectId')
            ->andWhere('version.scope = :scope')
            ->setParameter('objectClass', $objectClass)
            ->setParameter('objectId', $objectId)
            ->setParameter('scope', $scope)
            ->getQuery()
            ->getSingleScalarResult();

        return ((int) $lastVersion) + 1;
    }

    public function findLatest(string $objectClass, string $objectId, string $scope = 'default'): ?BusinessVersion
    {
        return $this->createQueryBuilder('version')
            ->andWhere('version.objectClass = :objectClass')
            ->andWhere('version.objectId = :objectId')
            ->andWhere('version.scope = :scope')
            ->setParameter('objectClass', $objectClass)
            ->setParameter('objectId', $objectId)
            ->setParameter('scope', $scope)
            ->orderBy('version.version', 'DESC')
            ->setMaxResults(1)
            ->getQuery()
            ->getOneOrNullResult();
    }

    public function findPublished(string $objectClass, string $objectId, string $scope = 'default'): ?BusinessVersion
    {
        return $this->createQueryBuilder('version')
            ->andWhere('version.objectClass = :objectClass')
            ->andWhere('version.objectId = :objectId')
            ->andWhere('version.scope = :scope')
            ->andWhere('version.status = :status')
            ->setParameter('objectClass', $objectClass)
            ->setParameter('objectId', $objectId)
            ->setParameter('scope', $scope)
            ->setParameter('status', BusinessVersion::STATUS_PUBLISHED)
            ->orderBy('version.version', 'DESC')
            ->setMaxResults(1)
            ->getQuery()
            ->getOneOrNullResult();
    }

    public function findPendingDraft(string $objectClass, string $objectId, string $scope): ?BusinessVersion
    {
        return $this->createQueryBuilder('version')
            ->andWhere('version.objectClass = :objectClass')
            ->andWhere('version.objectId = :objectId')
            ->andWhere('version.scope = :scope')
            ->andWhere('version.status = :status')
            ->andWhere('version.validFrom IS NOT NULL')
            ->setParameter('objectClass', $objectClass)
            ->setParameter('objectId', $objectId)
            ->setParameter('scope', $scope)
            ->setParameter('status', BusinessVersion::STATUS_DRAFT)
            ->orderBy('version.validFrom', 'ASC')
            ->addOrderBy('version.version', 'ASC')
            ->setMaxResults(1)
            ->getQuery()
            ->getOneOrNullResult();
    }

    /**
     * @param array{
     *     scope?: ?string,
     *     status?: ?string,
     *     objectClass?: ?string,
     *     objectId?: ?string,
     *     createdBy?: ?string,
     *     createdFrom?: ?\DateTimeImmutable,
     *     createdTo?: ?\DateTimeImmutable
     * } $filters
     */
    public function createAdminQueryBuilder(array $filters): QueryBuilder
    {
        $queryBuilder = $this->createQueryBuilder('version')
            ->orderBy('version.createdAt', 'DESC')
            ->addOrderBy('version.version', 'DESC');

        if (null !== ($filters['scope'] ?? null)) {
            $queryBuilder
                ->andWhere('version.scope = :scope')
                ->setParameter('scope', $filters['scope']);
        }

        if (null !== ($filters['status'] ?? null)) {
            $queryBuilder
                ->andWhere('version.status = :status')
                ->setParameter('status', $filters['status']);
        }

        if (null !== ($filters['objectClass'] ?? null)) {
            $queryBuilder
                ->andWhere('version.objectClass = :objectClass')
                ->setParameter('objectClass', $filters['objectClass']);
        }

        if (null !== ($filters['objectId'] ?? null)) {
            $queryBuilder
                ->andWhere('version.objectId LIKE :objectId')
                ->setParameter('objectId', '%'.$filters['objectId'].'%');
        }

        if (null !== ($filters['createdBy'] ?? null)) {
            $queryBuilder
                ->andWhere('version.createdBy LIKE :createdBy')
                ->setParameter('createdBy', '%'.$filters['createdBy'].'%');
        }

        if (null !== ($filters['createdFrom'] ?? null)) {
            $queryBuilder
                ->andWhere('version.createdAt >= :createdFrom')
                ->setParameter('createdFrom', $filters['createdFrom']);
        }

        if (null !== ($filters['createdTo'] ?? null)) {
            $queryBuilder
                ->andWhere('version.createdAt <= :createdTo')
                ->setParameter('createdTo', $filters['createdTo']);
        }

        return $queryBuilder;
    }

    /**
     * @return list<string>
     */
    public function findDistinctScopes(): array
    {
        return $this->findDistinctScalarValues('scope');
    }

    /**
     * @return list<string>
     */
    public function findDistinctObjectClasses(): array
    {
        return $this->findDistinctScalarValues('objectClass');
    }

    /**
     * @return list<BusinessVersion>
     */
    public function findObjectHistory(string $objectClass, string $objectId, string $scope): array
    {
        return $this->createQueryBuilder('version')
            ->andWhere('version.objectClass = :objectClass')
            ->andWhere('version.objectId = :objectId')
            ->andWhere('version.scope = :scope')
            ->setParameter('objectClass', $objectClass)
            ->setParameter('objectId', $objectId)
            ->setParameter('scope', $scope)
            ->orderBy('version.version', 'DESC')
            ->getQuery()
            ->getResult();
    }

    /**
     * @return list<BusinessVersion>
     */
    public function findDueDrafts(string $scope, \DateTimeImmutable $now, int $limit = 100): array
    {
        return $this->createQueryBuilder('version')
            ->andWhere('version.scope = :scope')
            ->andWhere('version.status = :status')
            ->andWhere('version.validFrom IS NOT NULL')
            ->andWhere('version.validFrom <= :now')
            ->setParameter('scope', $scope)
            ->setParameter('status', BusinessVersion::STATUS_DRAFT)
            ->setParameter('now', $now)
            ->orderBy('version.validFrom', 'ASC')
            ->addOrderBy('version.version', 'ASC')
            ->setMaxResults($limit)
            ->getQuery()
            ->getResult();
    }

    /**
     * @return list<string>
     */
    private function findDistinctScalarValues(string $field): array
    {
        $rows = $this->createQueryBuilder('version')
            ->select(sprintf('DISTINCT version.%s AS value', $field))
            ->andWhere(sprintf('version.%s IS NOT NULL', $field))
            ->orderBy('value', 'ASC')
            ->getQuery()
            ->getArrayResult();

        return array_values(array_filter(array_map(
            static fn (array $row): ?string => \is_string($row['value'] ?? null) && '' !== $row['value'] ? $row['value'] : null,
            $rows
        )));
    }
}
