<?php

namespace CpCreation\VitiCore\Bridge\Symfony\Bundle\Controller\Admin;

use DH\Auditor\Exception\AccessDeniedException;
use DH\Auditor\Exception\InvalidArgumentException;
use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Security;
use DH\Auditor\Provider\Doctrine\Persistence\Reader\Reader;
use DH\Auditor\Provider\Doctrine\Persistence\Schema\SchemaManager;
use DH\Auditor\Provider\Doctrine\Service\AuditingService;
use DH\AuditorBundle\Helper\UrlHelper;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;

#[Route('/audit')]
#[IsGranted(new Expression("is_granted('ROLE_ADMIN') or is_granted('ROLE_SUPER_ADMIN')"))]
final class AuditController extends AbstractController
{
    private const PAGE_SIZE = 25;

    public function __construct(
        private readonly Reader $reader,
    ) {
    }

    #[Route('', name: 'admin_audit')]
    public function index(): Response
    {
        $auditedEntities = $this->getAuditedEntities();

        return $this->render('@CpCreationVitiCore/admin/audit/index.html.twig', [
            'entities' => $auditedEntities,
        ]);
    }

    #[Route('/{entity}', name: 'admin_audit_history')]
    public function history(Request $request, string $entity): Response
    {
        $entityClass = UrlHelper::paramToNamespace($entity);
        $page = max(1, $request->query->getInt('page', 1));
        $objectId = $this->normalizeNullableString($request->query->get('objectId'));
        $type = $this->normalizeType($request->query->get('type'));

        if (!$this->reader->getProvider()->isAudited($entityClass)) {
            throw new NotFoundHttpException('Journal d\'audit introuvable.');
        }

        try {
            $query = $this->reader->createQuery($entityClass, [
                'object_id' => $objectId,
                'type' => $type,
                'page' => $page,
                'page_size' => self::PAGE_SIZE,
            ]);
            $paginator = $this->reader->paginate($query, $page, self::PAGE_SIZE);
        } catch (AccessDeniedException) {
            throw new AccessDeniedHttpException('Accès interdit au journal d\'audit.');
        } catch (InvalidArgumentException) {
            throw new NotFoundHttpException('Journal d\'audit introuvable.');
        }

        return $this->render('@CpCreationVitiCore/admin/audit/history.html.twig', [
            'entity' => [
                'class' => $entityClass,
                'label' => $this->formatEntityLabel($entityClass),
                'param' => UrlHelper::namespaceToParam($entityClass),
                'table' => $this->reader->getEntityTableName($entityClass),
            ],
            'filters' => [
                'objectId' => $objectId,
                'type' => $type,
            ],
            'paginator' => $paginator,
            'pageSize' => self::PAGE_SIZE,
        ]);
    }

    /**
     * @return list<array{class: string, label: string, param: string, table: string, count: int}>
     */
    private function getAuditedEntities(): array
    {
        $schemaManager = new SchemaManager($this->reader->getProvider());
        $auditedEntities = [];
        $scope = Security::VIEW_SCOPE;

        /** @var AuditingService[] $auditingServices */
        $auditingServices = $this->reader->getProvider()->getAuditingServices();
        foreach ($auditingServices as $auditingService) {
            $entitiesByTable = $schemaManager->getAuditableTableNames($auditingService->getEntityManager());
            foreach (array_keys($entitiesByTable) as $entityClass) {
                if (!$this->reader->getProvider()->isAudited($entityClass)) {
                    continue;
                }

                $roleChecker = $this->reader->getProvider()->getAuditor()->getConfiguration()->getRoleChecker();
                if (null !== $roleChecker && !$roleChecker($entityClass, $scope)) {
                    continue;
                }

                $auditedEntities[] = [
                    'class' => $entityClass,
                    'label' => $this->formatEntityLabel($entityClass),
                    'param' => UrlHelper::namespaceToParam($entityClass),
                    'table' => $this->reader->getEntityTableName($entityClass),
                    'count' => $this->countAudits($entityClass),
                ];
            }
        }

        usort(
            $auditedEntities,
            static fn (array $left, array $right): int => strcasecmp($left['label'], $right['label'])
        );

        return $auditedEntities;
    }

    private function countAudits(string $entityClass): int
    {
        try {
            return $this->reader->createQuery($entityClass, [
                'page_size' => 1,
            ])->count();
        } catch (\Throwable) {
            return 0;
        }
    }

    private function formatEntityLabel(string $entityClass): string
    {
        $shortName = substr($entityClass, (int) strrpos($entityClass, '\\') + 1);
        $label = preg_replace('/(?<!^)[A-Z]/', ' $0', $shortName) ?? $shortName;

        return trim($label);
    }

    private function normalizeNullableString(mixed $value): ?string
    {
        if (!\is_string($value)) {
            return null;
        }

        $value = trim($value);

        return '' !== $value ? $value : null;
    }

    private function normalizeType(mixed $type): ?string
    {
        if (!\is_string($type)) {
            return null;
        }

        return \in_array($type, ['insert', 'update', 'remove'], true) ? $type : null;
    }
}
