<?php

namespace KupShop\FeedsBundle\Controller;

use KupShop\AdminBundle\Util\ActivityLog;
use KupShop\FeedsBundle\Feed\IFeed;
use KupShop\FeedsBundle\FeedLocator;
use KupShop\FeedsBundle\FeedsBundle;
use KupShop\FeedsBundle\Utils\FeedRenderer;
use KupShop\FeedsBundle\Utils\FeedUtils;
use KupShop\KupShopBundle\Context\CountryContext;
use KupShop\KupShopBundle\Context\CurrencyContext;
use KupShop\KupShopBundle\Context\DomainContext;
use KupShop\KupShopBundle\Context\LanguageContext;
use KupShop\KupShopBundle\Util\CachingStreamedResponse;
use KupShop\KupShopBundle\Util\Logging\SentryLogger;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Annotation\Route;

class FeedController extends AbstractController
{
    use \DatabaseCommunication;

    public function __construct(
        private FeedLocator $feedLocator,
        private LanguageContext $languageContext,
        private CurrencyContext $currencyContext,
        private CountryContext $countryContext,
        private DomainContext $domainContext,
        private FeedUtils $feedUtils,
        private FeedRenderer $feedRenderer,
        private LoggerInterface $logger,
        private RequestStack $requestStack,
    ) {
    }

    /**
     * @Route("/feed/{feedID}/{hash}", requirements={"feedID":"[0-9]+", "hash":"[0-9a-zA-Z_-]+"})
     * @Route("/feed/{feedID}/{hash}.{format}", requirements={"feedID":"[0-9]+", "hash":".*", "format":"xml|json|csv|xlsx"})
     *
     * @return Response
     *
     * @throws \KupShop\FeedsBundle\Exceptions\UnknownFeedTypeException
     */
    public function exportAction(SentryLogger $sentryLogger, Request $request)
    {
        $feedID = $request->get('feedID');
        $hash = $request->get('hash');
        $limit = $request->get('limit');
        // output feed as a file (simple presence of this parameter means yes - might be empty)
        $file = $request->get('file');
        $format = $request->get('format', 'xml');
        $pretty = $request->get('pretty');
        $date_from = $request->get('date_from');
        $date_to = $request->get('date_to');
        $updateDownload = in_array($request->getRealMethod(), ['GET', 'POST']);

        $feedRow = sqlQueryBuilder()->select('*')->from('feeds')
            ->where('id = :id')->setParameter('id', (int) $feedID)
            ->execute()->fetch();
        if ($feedRow && $feedRow['hash'] === $hash) {
            if (!$feedRow['active'] && !getAdminUser()) {
                throw new NotFoundHttpException('Feed not active');
            }
            $feedRow['data'] = json_decode($feedRow['data'] ?? '{}', true);
            $feedRow['pretty'] = (($feedRow['data']['format'] ?? 'xml') == 'xml_pretty') || $pretty || getAdminUser();

            increaseMemoryLimit(1500);
            ini_set('max_execution_time', 600);
            ignore_user_abort(true);

            // clean & disable output buffering
            while (ob_get_level()) {
                ob_end_clean();
            }

            return $this->outputFeed(
                $this->feedLocator->getServiceByType($feedRow['type']),
                $feedRow,
                $limit,
                isset($file),
                (bool) getVal('skip_cache', null, false),
                $format,
                $updateDownload,
                $date_from,
                $date_to
            );
        } else {
            $data = ['Feed ID' => $feedID, 'Hash' => $hash, 'IP' => $_SERVER['REMOTE_ADDR']];
            if ($feedRow) {
                $data['Type'] = $feedRow['type'];
                $data['Name'] = $feedRow['name'];
                $data['Active'] = $feedRow['active'];
                $data['Language'] = $feedRow['id_language'] ?? 'independent';
                $data['Currency'] = $feedRow['id_currency'] ?? 'independent';
            }
            throw new NotFoundHttpException('Feed not found');
        }
    }

    public function outputFeed(
        IFeed $feed,
        array $feedRow,
        ?int $limit = null,
        bool $file = false,
        bool $skipCache = false,
        $format = 'xml',
        $updateDownload = true,
        $date_from = null,
        $date_to = null,
    ): Response {
        if ($feed::getType() == 'deprecated') {
            addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_IMPORT,
                "Stáhl se již ukončený (nekonfigurovatelný) feed '{$feedRow['name']}' (ID = {$feedRow['id']}). 
                Je nutné předělat feed na konfigurovatelný, stávající feed už se neaktualizuje.",
                tags: [FeedsBundle::LOG_TAG_FEED]);
        }

        // use mocked session storage for feeds to avoid errors "Headers already sent by ..."
        $this->requestStack->getCurrentRequest()?->setSession(
            new Session(new MockArraySessionStorage())
        );

        $feedName = ['feed', $feedRow['id']];

        $this->feedUtils->prepareContexts($feedRow);

        $feedName[] = $this->languageContext->getActiveId();
        $feedName[] = $this->currencyContext->getActiveId();
        $feedName[] = $this->countryContext->getActiveId();
        $feedName[] = $this->domainContext->getActiveId();

        $feedName = implode('_', $feedName).'.'.$format;

        // dates will differ between requests so we cant use cache
        if ($date_from || $date_to) {
            $skipCache = true;
        }

        // update feed info
        if (!getAdminUser() && !$skipCache && $updateDownload) {
            $this->updateSQL('feeds', ['last_download' => (new \DateTime())->format('Y-m-d H:i:s')], ['id' => $feedRow['id']]);
        }

        $response = new CachingStreamedResponse(function () use ($feed, $feedRow, $feedName, $limit, $format, $skipCache, $updateDownload) {
            $uid = '';
            if (is_null($limit)) {
                $uid = uniqid('', true);
                $this->logger->notice('Feed started generating ['.$feedRow['id'].'] "'.$feedName.'"', [
                    'uid' => $uid,
                    'id' => $feedRow['id'],
                    'cacheName' => $feedName,
                    'name' => $feedRow['name'],
                    'isAdmin' => getAdminUser() ? 'true' : 'false',
                    'skipCache' => $skipCache ? 'true' : 'false',
                ]);
            }
            $timeStart = getScriptTime();

            $this->feedRenderer->render($feed, $feedRow, $limit, $format);

            // update feed info
            $timestamp = (new \DateTime())->format('Y-m-d H:i:s');
            $updateData = [];
            // if not cached and without limit
            if (is_null($limit)) {
                $updateData['regenerated'] = $timestamp;
            }
            if (!getAdminUser() && !$skipCache && $updateDownload) {
                $updateData['last_download'] = $timestamp;
            }
            if (count($updateData)) {
                $this->updateSQL('feeds', $updateData, ['id' => $feedRow['id']]);
            }

            if (is_null($limit)) {
                $this->logger->notice('Feed finished ['.$feedRow['id'].'] "'.$feedName.'"', [
                    'uid' => $uid,
                    'id' => $feedRow['id'],
                    'cacheName' => $feedName,
                    'name' => $feedRow['name'],
                    'isAdmin' => getAdminUser() ? 'true' : 'false',
                    'skipCache' => $skipCache ? 'true' : 'false',
                    'format' => $format,
                    'duration' => getScriptTime() - $timeStart,
                    'peakMemory' => memory_get_peak_usage(true), // in bytes
                ]);
            }
        });

        if ($limit) {
            $response->setSkipCache(1);
            $response->setIsLimited(true);
        }

        if ($skipCache) {
            $response->setSkipCache(1);
        }

        if ($feedRow['expensive'] ?? false) {
            $response->setExpensive();
        } elseif (getAdminUser()) {
            // skip cache for admins with non-expensive feeds only
            $response->setSkipCache(1);
        }

        $response->setCacheName($feedName);
        if (isset($feedRow['data']['ttl']) && is_numeric($feedRow['data']['ttl'])) {
            // min TTL is 30 minutes
            $ttl = max(60 * 30, 60 * $feedRow['data']['ttl']);
        } else {
            $ttl = $feed->getTTL();
        }
        $response->setTtl($ttl);

        // get correct content type from formatter
        $contentType = $this->feedUtils->getFeedFormatter($format)?->getContentType() ?: "text/{$format}";

        // cannot use symfony Request::getMethod() nor getRealMethod() because Symfony\Component\HttpKernel\HttpCache\HttpCache::fetch() replaces HEAD with GET for some reason
        if (($_SERVER['REQUEST_METHOD'] ?? '') === 'HEAD') {
            $headResponse = new Response();
            $headResponse->headers->set('Content-Type', $contentType);
            $cacheFileName = $response->getCacheName();
            if (file_exists($cacheFileName) && (time() - filemtime($cacheFileName)) < $ttl) {
                $headResponse->headers->set('Content-Length', filesize($cacheFileName));
                $headResponse->headers->set('Last-Modified', gmdate('D, d M Y H:i:s ', filemtime($cacheFileName)).'GMT');
            } else {
                $headResponse->headers->set('Last-Modified', gmdate('D, d M Y H:i:s ', time()).'GMT');
            }

            return $headResponse;
        }

        $response->initialize();

        $response->headers->set('Content-Type', $contentType);
        if ($file) {
            $response->headers->set('Content-Description', 'File Transfer');
            $response->headers->set('Content-Disposition', "attachment; filename={$feedName}");
            $response->headers->set('Connection', 'Keep-Alive');
            $response->headers->set('Expires', '0');
        }

        return $response;
    }
}
