<?php
namespace apexl\Io\middleware\subscription;

use apexl\Config\Singleton;
use apexl\Io\modules\subscription\entities\subscriptionEntity;
use apexl\Io\modules\user\entities\userEntity;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Routing\RouteContext;

class subscriptionController implements MiddlewareInterface
{
    public function __construct(Singleton $config)
    {
        $this->config = $config;
    }
    
    public function process(Request $request, RequestHandler $handler): Response
    {
        //always allow OPTIONS
        $method = $request->getMethod();
        if($method == 'OPTIONS'){
            return $handler->handle($request);
        }

        // Allow access if no subscription defined in config
        if (!isset($this->config->app->subscription)) { 
            return $handler->handle($request);
        }

        // Require the restrict property to be present when using the subscription config parameter
        if (!isset($this->config->app->subscription->deny) || !is_array($this->config->app->subscription->deny)) {
            die('You must specify a valid subscription.deny property in config.json because you are using the config.subscription property');
        }

        $routeContext = RouteContext::fromRequest($request);
        $routeName = $routeContext->getRoute()->getName();

        // If this URL is not restricted, pass request
        if (!$this->checkRoutePatterns($routeName, $this->config->app->subscription->deny)){
            return $handler->handle($request);
        }

        $uid = $request->getAttribute('user'); // set in an earlier middleware
        $subscriptionEntity = new subscriptionEntity();

        // Does the user belong to a company,  and what route patterns are allowed by the subscriptions linked to that company - deny/allow?
        if (class_exists('\apexl\Io\modules\company\entities\companyEntity')) { // ToDo - there may be a better way of checking if the module is loaded?
            $companyEntity = new \apexl\Io\modules\company\entities\companyEntity();
            $company = $companyEntity->loadByUser($uid);
            $company = $company['data'];
            if ($company) {
                $subscriptions = $subscriptionEntity->getByCompany($company->id);
                if ($this->checkSubscriptionsForRouteAllowance($routeName, $subscriptions)) return $handler->handle($request);
            }
        }

        // Any user-defined subscriptions,  and what route patterns are allowed by the subscriptions linked to that user - deny/allow?
        $subscriptions = $subscriptionEntity->getByUser($uid);
        if ($this->checkSubscriptionsForRouteAllowance($routeName, $subscriptions, $subscriptionEntity)) return $handler->handle($request);

        return new \Slim\Psr7\Response(403); // Deny access
    }

    /**
     * Check if route allowed by subscriptions
     * @param $subscriptions
     * @return bool
     */
    protected function checkSubscriptionsForRouteAllowance($route, $subscriptions, $subscriptionEntity)
    {
        foreach ($subscriptions as $subscription) {
            if ($subscription && $subscription->enabled) {
                $subscriptionTypes = $subscriptionEntity->getTypesForSubscription($subscription->id);
                foreach ($subscriptionTypes as $type) {
                    if ($this->checkRouteAllowedBySubscriptionType($route, $type->subscriptionTypeId)) {
                        return true;
                    }
                }
            }
        }

        return false;
    }

    /**
     * Check whether a route is allowed by a given subscription in the config
     * @param $route
     * @param $subscriptionTypeId
     * @return bool
     */
    protected function checkRouteAllowedBySubscriptionType($route, $subscriptionTypeId)
    {
        if (!isset($configSubscriptionTypes)) {
            static $configSubscriptionTypes;
            $configSubscriptionTypes = isset($this->config->app->subscription->type) ? $this->config->app->subscription->type : [];
        }

        foreach ($configSubscriptionTypes as $configSubscriptionType) {
            if ($configSubscriptionType->id == $subscriptionTypeId) {
                if (isset($configSubscriptionType->allow) && is_array($configSubscriptionType->allow)) {
                    if ($this->checkRoutePatterns($route, $configSubscriptionType->allow)) return true;
                }
            }
        }

        return false;
    }

    /**
     * Is the given route name covered by the given set of route patterns?
     * @param $route
     * @param $routePatterns
     * @return bool
     */
    protected function checkRoutePatterns($route, $routePatterns)
    {
        foreach ($routePatterns as $pattern) {
            if ($this->checkRoutePattern($route, $pattern)) return true;
        }

        return false;
    }

    /**
     * Does the given route name match the given pattern?
     * @param $route
     * @param $pattern
     * @return bool
     */
    protected function checkRoutePattern($route, $pattern)
    {
        $parts = explode('*', $pattern);
        if ($parts[0] === $pattern) {
            return $route == $pattern;
        } else {
            return substr($route, 0, strlen($parts[0])) === $parts[0];
        }
    }
}