<?php
namespace Zeedhi\ZhuLog\Controller;

use Zeedhi\Framework\DTO\Response;
use Zeedhi\Framework\DTO\Request;
use Zeedhi\Framework\DataSource\DataSet;
use Zeedhi\Framework\Controller\Crud;
use Zeedhi\Framework\Cache\Type\MemcachedImpl;
use Zeedhi\Framework\DataSource\Manager\SQL\ManagerImpl;
use Zeedhi\ZhuLog\Logger\LoggerInfoProvider;
use Doctrine\DBAL\Connection;

class ZhuLogConfiguration extends Crud {

    /** @static ZH_LOG_PROD Table name. */
    public const ZH_LOG_PROD = 'ZH_LOG_PRODUCT_CONF';
    /** @static ZH_LOG_USER Table name. */
    public const ZH_LOG_USER = 'ZH_LOG_USER_CONF';
    /** @static COUNT Type of select. */
    public const COUNT = 'COUNT';

    /** @static PRODUCT_DATA_SOURCE_NAME The zh_log_product_conf table configuration file name. */
    private const PRODUCT_DATA_SOURCE_NAME = 'zhLogProdConf';

    /** @var string $datasourceName The dataSource configuration file name. */
    protected $dataSourceName = 'zhLogUserConf';

    /** @var Connection $connection The Connection object. */
    protected $connection;
    /** @var MemcachedImpl $memcached The Memcached Interface to deal with local server cache. */
    protected $memcached;

    /** @var string $productId The product's id. */
    protected $productId;
    /** @var string $organizationId The organization's id. */
    protected $organizationId;

    /**
     * Constructor...
     *
     * {@inheritdoc}
     *
     * @param ManagerImpl           $dataSourceManager   The data source manager.
     * @param LoggerInfroProvider   $loggerInfoProvider  The logger info provider.
     * @param MemcachedImpl         $memcached           The Memcached object.
     * @param Connection            $connection          The Connection object.
     * @param string                $cacheHost           The cache connection host name.
     * @param int                   $cachePort           The cache connection port.
     */
    public function __construct(
        ManagerImpl $dataSourceManager, 
        LoggerInfoProvider $loggerInfoProvider, 
        MemcachedImpl $memcached, 
        Connection $connection,
        string $cacheHost,
        int $cachePort
    ){
        $this->memcached = $memcached;
        $this->connection = $connection;

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

        $this->productId = $loggerInfoProvider->getProductId();
        $this->organizationId = $loggerInfoProvider->getOrganizationId();

        parent::__construct($dataSourceManager);
    }

    /**
     * Returns all allowed users or empty array if all are allowed.
     *
     * @param Request\Filter $request  The Request Object.
     * @param Response       $response The Response Object.
     */
    public function getAllowedUsers(Request\Filter $request, Response $response){
        parent::find($request, $response);
        $allowedUsers = $response->getDataSets()[0]->getRows();
        $this->memcached->save('allowedUsers', $allowedUsers);
    }

    /**
     * Update the allowed users list into memcached local server
     * and zh_log_user_conf table.
     *
     * @param Request\DataSet $request
     * @param Response        $response
     */
    public function updateAllowedUsers(Request\DataSet $request, Response $response){
        $dataSet = $request->getDataSet()->getRows()[0];
        $deny = $dataSet["deny"];
        $allowAll = $dataSet["all"];
        $users = $dataSet["users"];

        if($allowAll){
            $query = $this->connection->createQueryBuilder();

            $delete = $query->delete(self::ZH_LOG_USER)
                ->andWhere("PRODUCT_ID = {$this->productId}")
                ->andWhere("ORGANIZATION_ID = {$this->organizationId}");

            $delete->execute();

            $this->memcached->delete("allowedUsers_{$this->productId}");
        } else {
            $rows = [];
            foreach ($users as $user) {
                $userId = $user['CDOPERADOR'] ?? $user['USER_ID'];
                $email = $user['DSEMAILOPER'] ?? $user['EMAIL'] ?? null;
                $rows[] = [
                    'USER_ID'           => $userId,
                    'EMAIL'             => $email,
                    'PRODUCT_ID'        => $this->productId,
                    'ORGANIZATION_ID'   => $this->organizationId,
                    '__is_new'          => $this->shouldCreate(self::ZH_LOG_USER, $userId),
                ];
            }

            $dataset = new DataSet($this->dataSourceName, $rows);
            $newRequest = new Request\DataSet(
                $dataset,
                $request->getMethod(),
                $request->getRoutePath(),
                $request->getUserId()
            );
            if($deny){
                parent::delete($newRequest, $response);

                $query = $this->connection->createQueryBuilder();

                $select = $query->select('COUNT(USER_ID)')
                    ->from(self::ZH_LOG_USER)
                    ->andWhere("PRODUCT_ID = {$this->productId}")
                    ->andWhere("ORGANIZATION_ID = {$this->organizationId}");
                $result = $select->execute()->fetch();

                if(!reset($result)) $allowAll = true;
            } else {
                parent::save($newRequest, $response);
                $allowAll = false;
            }
        }

        $this->switchStatus(true, $request, $response, $allowAll);
        $this->allowUsers($users, $deny);
    }

    /**
     * Switches ZhuLog status for local memcached server and
     * zh_log_product_conf table.
     *
     * @param Request  $request  The Request Object.
     * @param Response $response The Response Object.
     */
    public function changeZhuLogStatus(Request\DataSet $request, Response $response){
        $status = $this->retrieveStatus($request, $response);
        $this->switchStatus(!$status, $request, $response, !$status);
        $this->setStatusOnResponse(!$status, $response);
    }

    /**
     * Do switches ZhuLog status.
     *
     * @param boolean         $status   The new state of ZhuLog.
     * @param Request\DataSet $request  The request object.
     * @param Response        $response The response object.
     */
    private function switchStatus($status, Request\DataSet $request, Response $response, $allowAll = false){
        $this->dataSourceName = self::PRODUCT_DATA_SOURCE_NAME;

        $row = [
            'PRODUCT_ID'        => $this->productId,
            'ORGANIZATION_ID'   => $this->organizationId,
            'IS_LOG_ACTIVE'     => $status ? 'Y' : 'N',
            'ALL_TOGGLED'       => $allowAll ? 'Y' : 'N',
            '__is_new'          => $this->shouldCreate(self::ZH_LOG_PROD),
        ];

        $dataset = new DataSet(self::PRODUCT_DATA_SOURCE_NAME, [$row]);
        $newRequest = new Request\DataSet(
            $dataset,
            $request->getMethod(),
            $request->getRoutePath(),
            $request->getUserId()
        );

        parent::save($newRequest, $response);

        if($status){
            $this->memcached->save($this->productId, $row['ALL_TOGGLED']);
        } else {
            $this->memcached->delete($this->productId);
        }
    }

    /**
     * Do allow users on memcache local server object.
     *
     * @param string[] $users A collection of users.
     */
    private function allowUsers(array $users = [], $deny = false){
        $key = "allowedUsers_{$this->productId}";
        $this->memcached->delete($key);
        if(!$deny){
            $query = $this->connection->createQueryBuilder();
            $select = $query->select('USER_ID')
                ->from(self::ZH_LOG_USER)
                ->where("PRODUCT_ID = {$this->productId}")
                ->andWhere("ORGANIZATION_ID = {$this->organizationId}");
            $fetch = $select->execute()->fetchAllAssociative();

            $this->memcached->save($key, json_encode($fetch));
        }
    }

    /**
     * Checks if the record should be created or updated.
     *
     * @param string $table The name of the table.
     *
     * @return boolean
     */
    private function shouldCreate($table, $userId = null){
        $select = $this->buildSQL(
            $this->connection,
            $table,
            $this->productId,
            $this->organizationId,
            "'$userId'"
        );
        $fetch = $select->execute()->fetch();

        return empty($fetch);
    }

    /**
     * Builds sql to check record pre existance.
     *
     * @param Connection $connection  The Connection object.
     * @param string     $table       The name of the table.
     * @param string     $userId      The user ID.
     *
     * @return Doctrine\DBAL\Statement
     */
    public static function buildSQL($connection, $table, $productId, $organizationId, $userId = null){
        switch($table){
            case self::ZH_LOG_PROD:
                $query = $connection->createQueryBuilder();

                return $query->select('PRODUCT_ID, IS_LOG_ACTIVE, ALL_TOGGLED')
                    ->from(self::ZH_LOG_PROD)
                    ->andWhere("PRODUCT_ID = {$productId}")
                    ->andWhere("ORGANIZATION_ID = {$organizationId}");
            case self::ZH_LOG_USER:
                $query = $connection->createQueryBuilder();

                return $query->select('USER_ID')
                    ->from(self::ZH_LOG_USER)
                    ->andWhere("USER_ID = {$userId}")
                    ->andWhere("PRODUCT_ID = {$productId}")
                    ->andWhere("ORGANIZATION_ID = {$organizationId}");
        }
    }

    /**
     * Gets the zh_log_prod all_toggled status.
     *
     * @param Request   $request    The request
     * @param Response  $response   The response
     */
    public function areAllActive(Request $request, Response $response){
        $fetch = self::buildSQL(
            $this->connection,
            self::ZH_LOG_PROD,
            $this->productId,
            $this->organizationId
        )
        ->execute()
        ->fetch();

        $result = $fetch && $fetch['ALL_TOGGLED'] === 'Y';
        $this->setStatusOnResponse($result, $response);
    }

    /**
     * Gets the zh_log_prod status.
     *
     * @param Request   $request    The request
     * @param Response  $response   The response
     */
    public function getStatus(Request $request, Response $response){
        $status = $this->retrieveStatus($request, $response);
        $this->setStatusOnResponse($status, $response);
    }

    /**
     * Sets the status to the zeedhi response.
     */
    private function setStatusOnResponse($status, $response){
        $dataset = new DataSet('status', [$status]);
        $response->addDataSet($dataset);
    }

    /**
     * Returns TRUE if log is active, FALSE otherwise.
     */
    private function retrieveStatus($request, $response){
        $fetch = self::buildSQL(
            $this->connection,
            self::ZH_LOG_PROD,
            $this->productId,
            $this->organizationId
        )
        ->execute()
        ->fetch();

        $result = $fetch && $fetch['IS_LOG_ACTIVE'] === 'Y';
        return $result;
    }
}