<?php

declare(strict_types=1);

/*
 * Chill is a software for social workers
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Chill\WopiBundle\Service\Wopi;

use ChampsLibres\WopiLib\Contract\Entity\Document;
use ChampsLibres\WopiLib\Contract\Service\DocumentLockManagerInterface;
use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Repository\StoredObjectRepository;
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
use Doctrine\ORM\EntityManagerInterface;
use loophp\psr17\Psr17Interface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\StreamInterface;
use Ramsey\Uuid\Uuid;
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Mime\MimeTypes;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;

final readonly class ChillDocumentManager implements DocumentManagerInterface
{
    private RequestInterface $request;

    public function __construct(
        private DocumentLockManagerInterface $documentLockManager,
        private EntityManagerInterface $entityManager,
        HttpMessageFactoryInterface $httpMessageFactory,
        private Psr17Interface $psr17,
        RequestStack $requestStack,
        private StoredObjectManagerInterface $storedObjectManager,
        private StoredObjectRepository $storedObjectRepository,
    ) {
        $this->request = $httpMessageFactory->createRequest($requestStack->getCurrentRequest());
    }

    public function create(array $data): Document
    {
        /** @var StoredObject $document */
        $document = (new ObjectNormalizer())->denormalize([], StoredObject::class);

        // Mime types / extension handling.
        $mimeTypes = new MimeTypes();
        $types = $mimeTypes->getMimeTypes($data['extension']);
        $mimeType = array_values($types)[0] ?? '';

        $this->entityManager->persist($document);
        $this->entityManager->flush();

        // TODO : Ask proper mapping.
        // Available: basename, name, extension, content, size
        $this->storedObjectManager->write($document, $data['content'], $mimeType);

        return $document;
    }

    public function deleteLock(Document $document): void
    {
        if (false === $this->documentLockManager->deleteLock($document, $this->request)) {
            throw new \RuntimeException('could not remove the lock');
        }
    }

    /**
     * @param string $documentFilename without extension !
     */
    public function findByDocumentFilename(string $documentFilename): ?Document
    {
        return $this->storedObjectRepository->findOneBy(
            [
                'filename' => $documentFilename,
            ]
        );
    }

    public function findByDocumentId(string $documentId): ?Document
    {
        return $this->storedObjectRepository->findOneBy(
            [
                'uuid' => Uuid::fromString($documentId),
            ]
        );
    }

    /**
     * @param StoredObject $document
     *
     * @return string the document filename with its extension
     */
    public function getBasename(Document $document): string
    {
        $exts = (new MimeTypes())->getExtensions($document->getType());

        if ([] === $exts) {
            throw new \Error('Unknown mimetype for stored document.');
        }

        return sprintf('%s.%s', $document->getPrefix(), reset($exts));
    }

    /**
     * @param StoredObject $document
     */
    public function getCreationDate(Document $document): \DateTimeInterface
    {
        return $document->getCreatedAt() ?? new \DateTimeImmutable('now');
    }

    /**
     * @param StoredObject $document
     */
    public function getDocumentId(Document $document): string
    {
        return (string) $document->getUuid();
    }

    /**
     * @param StoredObject $document
     */
    public function getLastModifiedDate(Document $document): \DateTimeInterface
    {
        return $this->storedObjectManager->getLastModified($document);
    }

    public function getLock(Document $document): string
    {
        return $this->documentLockManager->getLock($document, $this->request);
    }

    public function getSha256(Document $document): string
    {
        return base64_encode(hash('sha256', $this->getContent($document)));
    }

    public function getSize(Document $document): int
    {
        return \strlen($this->getContent($document));
    }

    public function getVersion(Document $document): string
    {
        // TODO ?
        return '0';
    }

    public function hasLock(Document $document): bool
    {
        return $this->documentLockManager->hasLock($document, $this->request);
    }

    public function lock(Document $document, string $lock): void
    {
        $this->documentLockManager->setLock($document, $lock, $this->request);
    }

    public function read(Document $document): StreamInterface
    {
        return $this
            ->psr17
            ->createStream($this->getContent($document));
    }

    public function remove(Document $document): void
    {
        throw new \RuntimeException('this is not implemented and should not happens');
    }

    public function rename(Document $document, string $requestedName): void
    {
        throw new \RuntimeException('this is not implemented and should not happens');
    }

    public function write(Document $document, array $properties = []): void
    {
        $this->setContent($document, $properties['content']);
    }

    private function getContent(StoredObject $storedObject): string
    {
        return $this->storedObjectManager->read($storedObject);
    }

    private function setContent(StoredObject $storedObject, string $content): void
    {
        $this->storedObjectManager->write($storedObject, $content);

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