<?php

declare(strict_types=1);

namespace App\Service;

use App\Entity\Document;
use App\Entity\DocumentVersion;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use RuntimeException;

final readonly class DocumentService
{
    public function __construct(
        private EntityManagerInterface $em,
        private TagRebuildJobService $tagRebuildJobService,
    ) {
    }

    /**
     * Creates a new document including version 1.
     */
    public function createDocument(
        string $title,
        string $filePath,
        User $user
    ): Document {
        $document = new Document();
        $document->setTitle(trim($title));
        $document->setCreatedBy($user);

        $version = new DocumentVersion();
        $version->setVersionNumber(1);
        $version->setFilePath($filePath);
        $version->setChecksum($this->calculateChecksum($filePath));
        $version->setCreatedBy($user);
        $version->setActive(true);

        $document->addVersion($version);
        $document->setCurrentVersion($version);

        $this->em->persist($document);
        $this->em->persist($version);
        $this->em->flush();

        return $document;
    }

    /**
     * Adds a new immutable version to an existing document.
     */
    public function addVersion(
        Document $document,
        string $filePath,
        User $user
    ): DocumentVersion {
        $nextVersionNumber = $this->getNextVersionNumber($document);

        $version = new DocumentVersion();
        $version->setVersionNumber($nextVersionNumber);
        $version->setFilePath($filePath);
        $version->setChecksum($this->calculateChecksum($filePath));
        $version->setCreatedBy($user);
        $version->setActive(false);

        $document->addVersion($version);

        $this->em->persist($version);
        $this->em->flush();

        return $version;
    }

    /**
     * Activates a document version and marks it for re-ingest.
     */
    public function activateVersion(DocumentVersion $version): void
    {
        $document = $version->getDocument();

        foreach ($document->getVersions() as $existingVersion) {
            $existingVersion->setActive(false);
        }

        $version->setActive(true);
        $document->setCurrentVersion($version);
        $version->setIngestStatus(DocumentVersion::INGEST_PENDING);

        $this->em->flush();
    }

    /**
     * Archives a document.
     *
     * If the document had tag assignments, the tag index is rebuilt so the
     * routing layer no longer works with an outdated active document set.
     */
    public function archive(Document $document): void
    {
        if ($document->getStatus() === Document::STATUS_ARCHIVED) {
            return;
        }

        $shouldRebuildTags = $this->hasTagAssignments($document);

        $document->archive();
        $this->em->flush();

        if ($shouldRebuildTags) {
            $this->triggerTagRebuildIfIdle();
        }
    }

    /**
     * Deletes a document.
     *
     * If the document had tag assignments, the tag index is rebuilt after the
     * removal so stale document references disappear from tag-based routing.
     */
    public function delete(Document $document): void
    {
        $shouldRebuildTags = $this->hasTagAssignments($document);

        $this->em->remove($document);
        $this->em->flush();

        if ($shouldRebuildTags) {
            $this->triggerTagRebuildIfIdle();
        }
    }

    /**
     * Calculates the SHA256 checksum for a file path.
     */
    private function calculateChecksum(string $filePath): string
    {
        $filePath = trim($filePath);

        if ($filePath === '') {
            throw new RuntimeException('File path must not be empty.');
        }

        if (!is_file($filePath)) {
            throw new RuntimeException('File not found for checksum.');
        }

        $checksum = hash_file('sha256', $filePath);

        if ($checksum === false) {
            throw new RuntimeException('Could not calculate file checksum.');
        }

        return $checksum;
    }

    /**
     * Determines the next version number for a document.
     */
    private function getNextVersionNumber(Document $document): int
    {
        $max = 0;

        foreach ($document->getVersions() as $version) {
            $max = max($max, $version->getVersionNumber());
        }

        return $max + 1;
    }

    private function hasTagAssignments(Document $document): bool
    {
        return $document->getDocumentTags()->count() > 0;
    }

    private function triggerTagRebuildIfIdle(): void
    {
        if (!$this->tagRebuildJobService->hasActiveJob()) {
            $this->tagRebuildJobService->enqueueAndStartAsync();
        }
    }
}