<?php

declare(strict_types=1);

namespace App\Ingest;

use App\Entity\Document;
use App\Entity\DocumentVersion;
use App\Index\IndexMetaManager;
use App\Knowledge\Ingest\KnowledgeIngestService;
use App\Service\LockService;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Uid\Uuid;

final readonly class IngestFlow
{
    public const CHUNK_LIMIT_HARD = 25000;
    private const CHUNK_LIMIT_WARN = 20000;

    public function __construct(
        private KnowledgeIngestService $knowledgeIngestService,
        private GuardrailValidator     $guardrailValidator,
        private ChunkWriteService      $chunkWriteService,
        private VectorRebuildService   $vectorRebuildService,
        private IndexMetaManager       $metaManager,
        private LockService            $lockService,
        private LoggerInterface        $logger,
        private EntityManagerInterface $em,
    )
    {
    }

    // =========================================================
    // DOCUMENT INGEST
    // =========================================================

    public function ingestDocumentVersion(DocumentVersion $version): void
    {
        $this->withLock(function () use ($version): void {

            $this->guardrailValidator->validateOrThrow();

            $version->setIngestStatus(DocumentVersion::INGEST_RUNNING);
            $this->em->flush();

            try {
                $documentId = $version->getDocument()->getId();
                if (!$documentId instanceof Uuid) {
                    throw new \RuntimeException('Document ID must be a Uuid instance');
                }

                // 1) Alte Chunks entfernen
                $this->chunkWriteService->compactByDocumentId($documentId);

                // 2) Existing Chunks nach Compaction zählen
                $existing = $this->chunkWriteService->countAllChunks();

                $incoming = 0;
                $warned = false;

                $generator = $this->knowledgeIngestService->buildChunkRecords($version);

                $wrappedGenerator = (function () use ($generator, $existing, &$incoming, &$warned) {
                    foreach ($generator as $record) {
                        $incoming++;
                        $total = $existing + $incoming;

                        if (!$warned && $total >= self::CHUNK_LIMIT_WARN) {
                            $warned = true;
                        }

                        if ($total > self::CHUNK_LIMIT_HARD) {
                            throw new \RuntimeException('Chunk limit exceeded.');
                        }

                        yield $record;
                    }
                })();

                // 3) Streaming Append
                $this->chunkWriteService->appendChunks($wrappedGenerator);

                $total = $existing + $incoming;

                if ($warned) {
                    $this->logger->warning('Chunk count approaching limit.', [
                        'existing' => $existing,
                        'incoming' => $incoming,
                        'total' => $total,
                        'document' => (string)$documentId,
                        'version' => (string)$version->getId(),
                    ]);
                }

                // 4) Vector Rebuild + Runtime Update
                $this->vectorRebuildService->rebuild();

                $version->setIngestStatus(DocumentVersion::INGEST_INDEXED);
                $this->em->flush();

            } catch (\Throwable $e) {
                $version->setIngestStatus(DocumentVersion::INGEST_FAILED);
                $this->em->flush();
                throw $e;
            }
        });
    }

    // =========================================================
    // GLOBAL REINDEX
    // =========================================================

    public function globalReindex(): void
    {
        $this->withLock(function (): void {

            // Global Reindex ist der Drift-Fix → keine Guardrail-Blockade hier.

            $activeDocuments = $this->em
                ->getRepository(Document::class)
                ->createQueryBuilder('d')
                ->where('d.status = :status')
                ->setParameter('status', Document::STATUS_ACTIVE)
                ->getQuery()
                ->getResult();

            if (empty($activeDocuments)) {
                throw new \RuntimeException('Global Reindex aborted: no active documents found.');
            }

            $existing = 0; // rewriteAll ersetzt alles
            $incoming = 0;
            $warned = false;

            $generator = $this->knowledgeIngestService->buildAllActiveChunkRecords();

            // 1) "Peek" ohne RAM: erstes Element ziehen
            $first = null;
            foreach ($generator as $record) {
                $first = $record;
                $incoming++;
                break;
            }

            if ($first === null) {
                throw new \RuntimeException('Global Reindex aborted: no chunks generated.');
            }

            // 2) Stream bauen, Limits prüfen
            $stream = (function () use ($first, $generator, $existing, &$incoming, &$warned) {
                // first
                $total = $existing + $incoming;
                if (!$warned && $total >= self::CHUNK_LIMIT_WARN) {
                    $warned = true;
                }
                if ($total > self::CHUNK_LIMIT_HARD) {
                    throw new \RuntimeException('Chunk limit exceeded.');
                }
                yield $first;

                foreach ($generator as $record) {
                    $incoming++;
                    $total = $existing + $incoming;

                    if (!$warned && $total >= self::CHUNK_LIMIT_WARN) {
                        $warned = true;
                    }

                    if ($total > self::CHUNK_LIMIT_HARD) {
                        throw new \RuntimeException('Chunk limit exceeded.');
                    }

                    yield $record;
                }
            })();

            // 3) Rewrite + Rebuild
            $this->chunkWriteService->rewriteAll($stream);

            if ($warned) {
                $this->logger->warning('Chunk count approaching limit after global reindex.', [
                    'incoming' => $incoming,
                    'total' => $incoming,
                ]);
            }

            $this->vectorRebuildService->rebuild();

            // Governance: Version erhöhen
            $this->metaManager->writeMetaForGlobalReindex();
        });
    }

    // =========================================================
    // DELETE FLOW
    // =========================================================

    public function deleteDocument(Uuid $documentId): void
    {
        $this->withLock(function () use ($documentId): void {

            $this->guardrailValidator->validateOrThrow();

            /** @var Document|null $document */
            $document = $this->em
                ->getRepository(Document::class)
                ->find($documentId);

            if (!$document) {
                throw new \RuntimeException('Document not found.');
            }

            // 1) Chunks entfernen
            $this->chunkWriteService->compactByDocumentId($documentId);

            // 2) FK-sicher löschen: currentVersion lösen (verhindert „Version zeigt noch auf DocumentVersion“)
            if (method_exists($document, 'getCurrentVersion') && method_exists($document, 'setCurrentVersion')) {
                if ($document->getCurrentVersion() !== null) {
                    $document->setCurrentVersion(null);
                    $this->em->flush();
                }
            }

            // 3) Dokument entfernen
            $this->em->remove($document);
            $this->em->flush();

            // 4) Vector rebuild + runtime update
            $this->vectorRebuildService->rebuild();
        });
    }

    // =========================================================
    // INTERNALS
    // =========================================================

    /**
     * @param callable():void $fn
     */
    private function withLock(callable $fn): void
    {
        try {
            $fn();
        } finally {
            $this->lockService->release();
        }
    }
}