<?php

declare(strict_types=1);

namespace App\Service\Admin;

use App\Entity\Document;
use App\Entity\DocumentTag;
use App\Entity\Tag;
use App\Service\TagRebuildJobService;
use App\Tag\TagService;
use App\Tag\TagTypes;
use Doctrine\ORM\EntityManagerInterface;
use RuntimeException;

final readonly class TagAdminService
{
    public function __construct(
        private EntityManagerInterface $em,
        private TagService $tagService,
        private TagRebuildJobService $jobs,
    ) {
    }

    public function getIndexData(): array
    {
        /** @var list<Tag> $tags */
        $tags = $this->em->getRepository(Tag::class)
            ->findBy([], ['type' => 'ASC', 'label' => 'ASC']);

        return [
            'tags' => $tags,
            'tagTypeChoices' => TagTypes::choices(),
            'documentCountByTagId' => $this->buildDocumentCountByTagId(),
            'latestJob' => $this->jobs->getLatestJob(),
            'hasActiveJob' => $this->jobs->hasActiveJob(),
        ];
    }

    public function create(
        string $slug,
        string $label,
        ?string $description,
        string $type = TagTypes::GENERIC,
    ): void {
        $this->tagService->create($slug, $label, $description, $type);
    }

    public function delete(string $id): void
    {
        $this->tagService->deleteById($id);
    }

    public function getAssignData(string $tagId): array
    {
        $tag = $this->findTagById($tagId);

        /** @var list<Document> $documents */
        $documents = $this->em->getRepository(Document::class)->findBy(
            ['status' => Document::STATUS_ACTIVE],
            ['title' => 'ASC']
        );

        $documentsData = array_map(
            static fn (Document $document): array => [
                'id' => (string) $document->getId(),
                'title' => $document->getTitle(),
            ],
            $documents
        );

        /** @var list<DocumentTag> $existingRelations */
        $existingRelations = $this->em
            ->getRepository(DocumentTag::class)
            ->findBy(['tag' => $tag]);

        $activeDocumentIds = array_map(
            static fn (Document $document): string => (string) $document->getId(),
            $documents
        );

        $assignedDocIds = [];

        foreach ($existingRelations as $relation) {
            $documentId = (string) $relation->getDocument()->getId();

            if (in_array($documentId, $activeDocumentIds, true)) {
                $assignedDocIds[] = $documentId;
            }
        }

        return [
            'tag' => $tag,
            'documents' => $documentsData,
            'assignedDocIds' => array_values(array_unique($assignedDocIds)),
            'tagTypeChoices' => TagTypes::choices(),
            'latestJob' => $this->jobs->getLatestJob(),
            'hasActiveJob' => $this->jobs->hasActiveJob(),
        ];
    }

    public function syncAssignments(string $tagId, array $selectedDocIds): void
    {
        $tag = $this->findTagById($tagId);
        $this->tagService->syncTagDocuments($tag, $selectedDocIds);
    }

    private function findTagById(string $tagId): Tag
    {
        $tagId = trim($tagId);

        if ($tagId === '') {
            throw new RuntimeException('Tag nicht gefunden.');
        }

        $tag = $this->em->getRepository(Tag::class)->find($tagId);

        if (!$tag instanceof Tag) {
            throw new RuntimeException('Tag nicht gefunden.');
        }

        return $tag;
    }

    /**
     * @return array<string, int>
     */
    private function buildDocumentCountByTagId(): array
    {
        $rows = $this->em->createQueryBuilder()
            ->select('t AS tag', 'COUNT(d.id) AS documentCount')
            ->from(Tag::class, 't')
            ->leftJoin(DocumentTag::class, 'dt', 'WITH', 'dt.tag = t')
            ->leftJoin('dt.document', 'd', 'WITH', 'd.status = :status')
            ->groupBy('t.id')
            ->setParameter('status', Document::STATUS_ACTIVE)
            ->getQuery()
            ->getResult();

        $counts = [];

        foreach ($rows as $row) {
            $tag = $row[0] ?? $row['tag'] ?? null;
            $documentCount = (int) ($row['documentCount'] ?? 0);

            if (!$tag instanceof Tag) {
                continue;
            }

            $counts[$tag->getId()->toRfc4122()] = $documentCount;
        }

        return $counts;
    }
}