<?php

namespace KupShop\XMLFeedBundle\Controller;

use KupShop\FeedsBundle\Exceptions\UnknownFeedTypeException;
use KupShop\FeedsBundle\Feed\IFeed;
use KupShop\FeedsBundle\FeedLocator;
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\Context\UserContext;
use KupShop\KupShopBundle\Util\CachingStreamedResponse;
use KupShop\KupShopBundle\Util\Contexts;
use KupShop\XMLFeedBundle\Util\XMLFeedUtil;
use KupShop\XMLFeedBundle\View\B2BInfoView;
use Psr\Log\LoggerInterface;
use Query\Operator;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class B2BController extends AbstractController
{
    public function __construct(
        private FeedLocator $feedLocator,
        private FeedRenderer $feedRenderer,
        private FeedUtils $feedUtils,
        private XMLFeedUtil $xmlFeedUtil,
        private LoggerInterface $logger,
    ) {
    }

    /**
     * @Route("/uzivatel/b2b/")
     *
     * @return Response
     */
    public function userInfoAction(B2BInfoView $view)
    {
        return $view->getResponse();
    }

    /**
     * @Route("/xmlfeed/{feed}/{token}/", requirements={"feed":"([a-zA-Z0-9-]+)", "token":"([a-zA-Z0-9-]+)"})
     *
     * @param string $feed
     * @param        $token string security token
     *
     * @return Response
     */
    public function feedAction(Request $request, $feed, $token)
    {
        $limit = $request->get('limit');

        if ($feed == 'b2b' && $token) {
            $user = \User::createFromFeedToken($token);

            if ($user) {
                return $this->getFeed($user, null, $limit);
            }
        }

        return new RedirectResponse(createScriptURL([
            'URL' => 'launch.php',
            's' => 'orders',
            'ESCAPE' => 'NO',
        ]));
    }

    /**
     * @Route("/xmlfeed/{feed}/{token}/{feedID}", requirements={"feed":"([a-zA-Z0-9-]+)", "token":"([a-zA-Z0-9-]+)", "feedID":"([0-9]+)"})
     *
     * @param string $feed
     * @param        $token  string security token
     * @param        $feedID int
     *
     * @return Response
     */
    public function specificFeedAction(Request $request, $feed, $token, int $feedID)
    {
        $limit = $request->get('limit');

        if ($feed == 'b2b' && $token) {
            $user = \User::createFromFeedToken($token);

            if ($user) {
                return $this->getFeed($user, $feedID, $limit);
            }
        }

        return new RedirectResponse(createScriptURL([
            'URL' => 'launch.php',
            's' => 'orders',
            'ESCAPE' => 'NO',
        ]));
    }

    private function getFeed(\User $user, ?int $feedID = null, ?int $limit = null)
    {
        $safeFeedID = null;
        $multipleFeedsEnabled = findModule(\Modules::XML_FEEDS_B2B, \Modules::SUB_FEED_IDS);
        $defaultB2BFeeds = $this->xmlFeedUtil->getDefaultFeedIds();
        $enabledFeedsIDs = array_unique(array_merge($defaultB2BFeeds, $user->custom_data['b2b_feeds'] ?? []));
        if (isset($feedID) && $multipleFeedsEnabled && in_array($feedID, $enabledFeedsIDs)) {
            $safeFeedID = $feedID;
        } elseif (!isset($feedID)) {
            $safeFeedID = findModule(\Modules::XML_FEEDS_B2B, 'feed_id') ?? null;
        }

        $feedRow = sqlQueryBuilder()
            ->select('*')
            ->from('feeds')
            ->where(Operator::equals(['id' => $safeFeedID]))
            ->execute()->fetchAssociative();

        if (!$feedRow) {
            return new Response('Požadovaný B2BFeed není nakonfigurován');
        }

        $feedRowData = $feedRow['data'] = json_decode($feedRow['data'], true);

        try {
            $feed = $this->feedLocator->getServiceByType($feedRow['type']);
        } catch (UnknownFeedTypeException $e) {
            return new Response('Chybná konfigurace pro B2BFeed');
        }

        return $this->outputFeed($user, $feed, $feedRow, $feedRowData['format'], $limit, getVal('download') || getVal('file'));
    }

    protected function prepareContexts(\User $user, array $feedRow): void
    {
        $this->feedUtils->prepareContexts($feedRow);

        Contexts::get(UserContext::class)->activate($user);
        $user->activateUser();

        $countryContext = Contexts::get(CountryContext::class);
        if (!isset($feedRow['id_country']) && !empty($user->country)) {
            $countryContext->activate($user->country);
        }
    }

    public function outputFeed(\User $user, IFeed $feed, array $feedRow, string $format, ?int $limit, bool $download = false): Response
    {
        increaseMemoryLimit(1500);
        ini_set('max_execution_time', 600);
        ignore_user_abort(true);

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

        $this->prepareContexts($user, $feedRow);

        $feedName = ['feed', $feedRow['id'], 'b2b', $user->id];
        $feedName[] = Contexts::get(LanguageContext::class)->getActiveId();
        $feedName[] = Contexts::get(CurrencyContext::class)->getActiveId();
        $feedName[] = Contexts::get(CountryContext::class)->getActiveId();
        $feedName[] = Contexts::get(DomainContext::class)->getActiveId();
        $feedName = implode('_', $feedName).'.'.$format;

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

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

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

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

        if (getVal('skip_cache', null, false)) {
            $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 ($download) {
            $response->headers->set('Content-Description', 'File Transfer');
            $response->headers->set('Content-Disposition', 'attachment; filename=feed.'.$format);
            $response->headers->set('Connection', 'Keep-Alive');
            $response->headers->set('Expires', '0');
        }

        return $response;
    }
}
