<?php

/*
 * This file is part of Sulu.
 *
 * (c) Sulu GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\MediaBundle\Controller;

use Sulu\Bundle\MediaBundle\Admin\MediaAdmin;
use Sulu\Bundle\MediaBundle\Entity\Collection;
use Sulu\Bundle\MediaBundle\Entity\FileVersion;
use Sulu\Bundle\MediaBundle\Entity\MediaInterface;
use Sulu\Bundle\MediaBundle\Entity\MediaRepositoryInterface;
use Sulu\Bundle\MediaBundle\Media\DispositionType\DispositionTypeResolver;
use Sulu\Bundle\MediaBundle\Media\Exception\FileVersionNotFoundException;
use Sulu\Bundle\MediaBundle\Media\Exception\ImageProxyException;
use Sulu\Bundle\MediaBundle\Media\Exception\MediaException;
use Sulu\Bundle\MediaBundle\Media\FormatCache\FormatCacheInterface;
use Sulu\Bundle\MediaBundle\Media\FormatManager\FormatManagerInterface;
use Sulu\Bundle\MediaBundle\Media\Manager\MediaManagerInterface;
use Sulu\Bundle\MediaBundle\Media\Storage\StorageInterface;
use Sulu\Component\PHPCR\PathCleanupInterface;
use Sulu\Component\Security\Authorization\PermissionTypes;
use Sulu\Component\Security\Authorization\SecurityCheckerInterface;
use Sulu\Component\Security\Authorization\SecurityCondition;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class MediaStreamController
{
    /**
     * @var FormatManagerInterface
     */
    protected $formatManager;

    /**
     * @var FormatCacheInterface
     */
    protected $formatCache;

    /**
     * @var MediaManagerInterface
     */
    protected $mediaManager;

    /**
     * @var StorageInterface
     */
    protected $storage;

    /**
     * @var DispositionTypeResolver
     */
    protected $dispositionTypeResolver;

    /**
     * @var MediaRepositoryInterface
     */
    protected $mediaRepository;

    /**
     * @var PathCleanupInterface
     */
    protected $pathCleaner;

    /**
     * @var SecurityCheckerInterface|null
     */
    protected $securityChecker;

    public function __construct(
        DispositionTypeResolver $dispositionTypeResolver,
        MediaRepositoryInterface $mediaRepository,
        PathCleanupInterface $pathCleaner,
        FormatManagerInterface $formatManager,
        FormatCacheInterface $formatCache,
        MediaManagerInterface $mediaManager,
        StorageInterface $storage,
        ?SecurityCheckerInterface $securityChecker = null
    ) {
        $this->dispositionTypeResolver = $dispositionTypeResolver;
        $this->mediaRepository = $mediaRepository;
        $this->pathCleaner = $pathCleaner;
        $this->formatManager = $formatManager;
        $this->formatCache = $formatCache;
        $this->mediaManager = $mediaManager;
        $this->storage = $storage;
        $this->securityChecker = $securityChecker;
    }

    /**
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function getImageAction(Request $request)
    {
        try {
            if (\ob_get_length()) {
                \ob_end_clean();
            }

            $url = $request->getPathInfo();

            $mediaProperties = $this->formatCache->analyzedMediaUrl($url);

            return $this->formatManager->returnImage(
                $mediaProperties['id'],
                $mediaProperties['format'],
                $mediaProperties['fileName']
            );
        } catch (ImageProxyException $e) {
            throw new NotFoundHttpException('Image create error. Code: ' . $e->getCode());
        }
    }

    /**
     * @param int $id
     *
     * @return Response
     */
    public function downloadAction(Request $request, $id)
    {
        try {
            if (\ob_get_length()) {
                \ob_end_clean();
            }

            $version = $request->get('v', null);
            $noCount = $request->get('no-count', false);

            $fileVersion = $this->getFileVersion($id, $version);

            if (!$fileVersion) {
                return new Response(null, 404);
            }

            if ($this->securityChecker) {
                $this->securityChecker->checkPermission(
                    new SecurityCondition(
                        MediaAdmin::SECURITY_CONTEXT,
                        null,
                        Collection::class,
                        $fileVersion->getFile()->getMedia()->getCollection()->getId()
                    ),
                    PermissionTypes::VIEW
                );
            }

            if ($request->query->has('inline')) {
                $forceInline = (bool) $request->get('inline', false);
                $dispositionType = $forceInline ? ResponseHeaderBag::DISPOSITION_INLINE : ResponseHeaderBag::DISPOSITION_ATTACHMENT;
            } else {
                $dispositionType = $this->dispositionTypeResolver
                    ->getByMimeType($fileVersion->getMimeType());
            }

            if (!$noCount) {
                $this->mediaManager->increaseDownloadCounter($fileVersion->getId());
            }

            $response = $this->getFileResponse($fileVersion, $request->getLocale(), $dispositionType);

            return $response;
        } catch (MediaException $e) {
            throw new NotFoundHttpException('File not found: ' . $e->getCode() . ' ' . $e->getMessage());
        }
    }

    protected function getFileResponse(
        FileVersion $fileVersion,
        string $locale,
        string $dispositionType = ResponseHeaderBag::DISPOSITION_ATTACHMENT
    ): Response {
        $storageOptions = $fileVersion->getStorageOptions();

        $storageType = $this->storage->getType($storageOptions);

        if (StorageInterface::TYPE_REMOTE === $storageType) {
            $response = new RedirectResponse($this->storage->getPath($storageOptions), 302);
            $response->setPrivate();

            return $response;
        } elseif (StorageInterface::TYPE_LOCAL === $storageType) {
            return $this->createBinaryFileResponse($fileVersion, $this->storage, $locale, $dispositionType);
        }

        throw new \RuntimeException(\sprintf('Storage type "%s" not supported.', $storageType));
    }

    private function createBinaryFileResponse(
        FileVersion $fileVersion,
        StorageInterface $storage,
        string $locale,
        string $dispositionType
    ): BinaryFileResponse {
        $fileName = $fileVersion->getName();
        $fileSize = $fileVersion->getSize();
        $storageOptions = $fileVersion->getStorageOptions();
        $mimeType = $fileVersion->getMimeType();
        $lastModified = $fileVersion->getCreated(); // use created as file itself is not changed when entity is changed

        $response = new BinaryFileResponse($storage->getPath($storageOptions));

        // Prepare headers
        $disposition = $response->headers->makeDisposition(
            $dispositionType,
            $fileName,
            $this->cleanUpFileName($fileName, $locale, $fileVersion->getExtension())
        );

        // Set headers for
        $file = $fileVersion->getFile();
        if ($fileVersion->getVersion() !== $file->getVersion()) {
            $latestFileVersion = $file->getLatestFileVersion();

            $response->headers->set(
                'Link',
                \sprintf(
                    '<%s>; rel="canonical"',
                    $this->mediaManager->getUrl(
                        $file->getMedia()->getId(),
                        $latestFileVersion->getName(),
                        $latestFileVersion->getVersion()
                    )
                )
            );
            $response->headers->set('X-Robots-Tag', 'noindex, follow');
        }

        // Set headers
        $response->headers->set('Content-Type', !empty($mimeType) ? $mimeType : 'application/octet-stream');
        $response->headers->set('Content-Disposition', $disposition);
        $response->headers->set('Content-length', $fileSize);
        $response->headers->set('Last-Modified', $lastModified->format('D, d M Y H:i:s \G\M\T'));

        return $response;
    }

    /**
     * @param int $id
     * @param int $version
     *
     * @return FileVersion|null
     *
     * @throws \Sulu\Bundle\MediaBundle\Media\Exception\FileVersionNotFoundException
     */
    protected function getFileVersion($id, $version)
    {
        /** @var MediaInterface $mediaEntity */
        $mediaEntity = $this->mediaRepository->findMediaById($id);

        if (!$mediaEntity) {
            return null;
        }

        $file = $mediaEntity->getFiles()[0];

        if (!$file) {
            return null;
        }

        if (!$version) {
            $version = $mediaEntity->getFiles()[0]->getVersion();
        }

        $fileVersion = $file->getFileVersion((int) $version);

        if (!$fileVersion) {
            throw new FileVersionNotFoundException($id, $version);
        }

        return $fileVersion;
    }

    /**
     * Cleaned up filename.
     *
     * @param string $fileName
     * @param string $locale
     * @param string $extension
     *
     * @return string
     */
    private function cleanUpFileName($fileName, $locale, $extension)
    {
        $pathInfo = \pathinfo($fileName);
        $cleanedFileName = $this->pathCleaner->cleanup($pathInfo['filename'], $locale);
        if ($extension) {
            $cleanedFileName .= '.' . $extension;
        }

        return $cleanedFileName;
    }
}
