<?php
namespace Zeedhi\ZhuLog\Listeners;

use Zeedhi\Framework\Events\PreDispatch\Listener as PreDispatchListener;
use Zeedhi\Framework\Cache\Type\MemcachedImpl;
use Zeedhi\Framework\Events\AbstractEvent;
use Zeedhi\Framework\Events\Listener;
use Doctrine\DBAL\Connection;
use Zeedhi\Framework\DTO\Request;
use Zeedhi\Framework\HTTP;

use Zeedhi\ZhuLog\Controller\ZhuLogConfiguration;
use Zeedhi\ZhuLog\Logger\Logger;

class OnRequest extends PreDispatchListener {

    const PREFIX = 'Zeedhi\Framework\DTO\Request\\';

    /** @var string $baseUri Defines the base of the project for http requests. */
    private static $baseUri;
    /** @var Logger $logger The logger object. */
    protected $logger;
    /** @var MemcachedImpl $memcached The Memcached object. */
    protected $memcached;
    /** @var Connection $connection Connection object. */
    protected $connection;
    /** @var AbstractEvent $postDispatchEvent Post Dispatch Event object. */
    protected $postDispatchEvent;
    /** @var Listener $beforeResponseListener Before Response Listener object. */
    protected $beforeResponseListener;
    /** @var AbstractEvent $onExceptionEvent On Exception Event object. */
    protected $onExceptionEvent;
    /** @var Listener $onExceptionListener On Exception Listener object. */
    protected $onExceptionListener;
    /** @var array $skippedRoutes Defines if the event dealing shall be skipped or not. */
    private $skippedRoutes;

    /**
     * Constructor...
     *
     * @param Logger        $logger                 The logger object.
     * @param MemcachedImpl $memcached              The Memcached object.
     * @param Connection    $connection             Connection object.
     * @param AbstractEvent $postDispatchEvent      Post Dispach Event object.
     * @param Listener      $beforeResponseListener Post Dispatch Listener object.
     * @param AbstractEvent $onExceptionEvent       On Exception Event object.
     * @param Listener      $onExceptionListener    On Exception Listener object.
     * @param string        $baseUri                The project's baseUri.
     * @param array         $skippedRoutes          Array of skipped routes.
     * @param string        $cacheHost              The cache connection host name.
     * @param int           $cachePort              The cache connection port.
     */
    public function __construct(
        Logger $logger,
        MemcachedImpl $memcached,
        Connection $connection,
        AbstractEvent $postDispatchEvent,
        Listener $beforeResponseListener,
        AbstractEvent $onExceptionEvent,
        Listener $onExceptionListener,
        string $baseUri,
        array $skippedRoutes = [],
        string $cacheHost,
        int $cachePort
    ){
        $this->logger = $logger;
        $this->memcached = $memcached;
        $this->connection = $connection;
        $this->postDispatchEvent = $postDispatchEvent;
        $this->beforeResponseListener = $beforeResponseListener;
        $this->onExceptionEvent = $onExceptionEvent;
        $this->onExceptionListener = $onExceptionListener;
        $this->skippedRoutes = $skippedRoutes;

        self::$baseUri = $baseUri;

        $this->memcached->getMemcached()->addServer($cacheHost, $cachePort);
    }

    /**
     * Checks if is FilterData request.
     *
     * @param string $type The Request type.
     *
     * @return boolean
     */
    private function isFilter($type) {
        return $type === Request::TYPE_FILTER;
    }

    /**
     * Checks if the actual route should be skipped.
     *
     * @param string   $uri           The actual called uri.
     *
     * @return boolean
     */
    private function toSkip(string $uri) {
        foreach ($this->skippedRoutes as $route) {
            if($route === $uri) return true;
        }
        return false;
    }

    /**
     * Checks if zhulog status is cached.
     *
     * @param string $productId      The product ID.
     * @param string $organizationId The organization ID.
     * @param string $userId         The user ID.
     *
     * @return boolean
     */
    private function isCached($productId, $organizationId, $userId) {
        return $this->memcached->contains($productId) || $this->populateCache($productId, $organizationId, $userId);
    }

    /**
     * Checks if the user is allowed to log.
     *
     * @param string $productId      The product ID.
     * @param string $userId         The user ID.
     *
     * @return boolean
     */
    private function isAllowed($productId, $userId, $cached) {
        if(!$cached) return false;

        if($this->memcached->fetch($productId) === 'Y') return true;

        $key = "allowedUsers_{$productId}";
        $data = null;
        if($this->memcached->contains($key)){
            $data = $this->memcached->fetch($key);
            if(strlen($data) === 0) return false;

            $allowedUsers = json_decode($data, true);
            return empty($allowedUsers) || $this->inArray($userId, $allowedUsers);
        }
    }

    /**
     * Returns TRUE if user id is found at allowed users array.
     */
    private function inArray($userId, $allowedUsers) {
        foreach($allowedUsers as $allowedUser){
            if($userId === $allowedUser['USER_ID']) return true;
        }
    }

    /**
     * Prevents useless event calls.
     * 
     * @return bool
     */
    private function preventUselessLog() {
        $request = HTTP\Request::initfromGlobals();
        $type = $request->getRequestType();
        $uri = self::cleanupUri($request->getRequestUri());

        if(!$this->toSkip($uri) && !$this->isFilter($type)){
            list($userId, $organizationId, $productId) = $this->logger->getUserData();
    
            $cached = $this->isCached($productId, $organizationId, $userId);
            $cached = $cached && $this->memcached->fetch($productId);
    
            return !$this->isAllowed($productId, $userId, $cached);
        }

        return true;
    }

    /**
     * Unsets BeforeResponse Listener instance
     * from PostDipatch Event array of listeners.
     */
    private function setEvents() {
        $this->postDispatchEvent->addListener($this->beforeResponseListener);
        $this->onExceptionEvent->addListener($this->onExceptionListener);
    }

    /**
     * {@inheritdoc}
     */
    public function preDispatch(Request $request) {
        if (!$this->preventUselessLog()) {
            $uri = $request->getRoutePath();
            $method = $request->getMethod();
            $requestType = "Empty";

            switch(get_class($request)){
                case self::PREFIX.Request::TYPE_DATA_SET:
                    $requestType = Request::TYPE_DATA_SET;
                    break;
                case self::PREFIX.Request::TYPE_ROW:
                    $requestType = Request::TYPE_ROW;
            }

            $this->logger->logRequest($uri, $method, $requestType);
            
            $this->setEvents();
        }
    }

    /**
     * Populates memcached if table has data.
     *
     * @param string $productId      The product ID.
     * @param string $organizationId The organization ID.
     * @param string $userId         The user ID.
     *
     * @return boolean
     */
    private function populateCache($productId, $organizationId, $userId) {
        $prod = ZhuLogConfiguration::buildSQL(
            $this->connection,
            ZhuLogConfiguration::ZH_LOG_PROD,
            $productId,
            $organizationId,
            $userId
        );
        $prodfetch = $prod->execute()->fetch();
        $result = $prodfetch['IS_LOG_ACTIVE'] === 'Y';
        $allowAll = $prodfetch['ALL_TOGGLED'];

        if($result){
            $this->memcached->save($productId, $allowAll);

            if(!$allowAll){
                $select = ZhuLogConfiguration::buildSQL(
                    $this->connection,
                    ZhuLogConfiguration::ZH_LOG_USER,
                    $productId,
                    $organizationId,
                    $userId
                );
                $fetch = $select->execute()->fetchAllAssociative();

                $this->memcached->save("allowedUsers_{$productId}", json_encode($fetch));
            }
        }

        return $result;
    }

    /**
     * Return a URI cleanup
     *
     * @param $requestURI
     *
     * @return mixed
     */
    public static function cleanupUri($requestURI) {
        return self::removeParameters(self::removeBaseUri($requestURI));
    }

    /**
     * @param $requestURI
     *
     * @return mixed
     */
    private static function removeBaseUri($requestURI) {
        $baseUri = rtrim(self::$baseUri, ' /');
        return preg_replace('#(.*)(' . $baseUri . ')#', '', $requestURI);
    }

    /**
     * @param $uri
     *
     * @return mixed
     */
    private static function removeParameters($uri) {
        $uris = explode("?", $uri);
        return $uris[0];
    }
}