<?php

namespace WappoVendor\Illuminate\Session\Middleware;

use Closure;
use WappoVendor\Illuminate\Contracts\Session\Session;
use WappoVendor\Illuminate\Http\Request;
use WappoVendor\Illuminate\Routing\Route;
use WappoVendor\Illuminate\Session\SessionManager;
use WappoVendor\Illuminate\Support\Carbon;
use WappoVendor\Illuminate\Support\Facades\Date;
use WappoVendor\Symfony\Component\HttpFoundation\Cookie;
use WappoVendor\Symfony\Component\HttpFoundation\Response;
class StartSession
{
    /**
     * The session manager.
     *
     * @var \Illuminate\Session\SessionManager
     */
    protected $manager;
    /**
     * The callback that can resolve an instance of the cache factory.
     *
     * @var callable|null
     */
    protected $cacheFactoryResolver;
    /**
     * Create a new session middleware.
     *
     * @param  \Illuminate\Session\SessionManager  $manager
     * @param  callable|null  $cacheFactoryResolver
     * @return void
     */
    public function __construct(SessionManager $manager, callable $cacheFactoryResolver = null)
    {
        $this->manager = $manager;
        $this->cacheFactoryResolver = $cacheFactoryResolver;
    }
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if (!$this->sessionConfigured()) {
            return $next($request);
        }
        $session = $this->getSession($request);
        if ($this->manager->shouldBlock() || $request->route() instanceof Route && $request->route()->locksFor()) {
            return $this->handleRequestWhileBlocking($request, $session, $next);
        }
        return $this->handleStatefulRequest($request, $session, $next);
    }
    /**
     * Handle the given request within session state.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Illuminate\Contracts\Session\Session  $session
     * @param  \Closure  $next
     * @return mixed
     */
    protected function handleRequestWhileBlocking(Request $request, $session, Closure $next)
    {
        if (!$request->route() instanceof Route) {
            return;
        }
        $lockFor = $request->route() && $request->route()->locksFor() ? $request->route()->locksFor() : 10;
        $lock = $this->cache($this->manager->blockDriver())->lock('session:' . $session->getId(), $lockFor)->betweenBlockedAttemptsSleepFor(50);
        try {
            $lock->block(!is_null($request->route()->waitsFor()) ? $request->route()->waitsFor() : 10);
            return $this->handleStatefulRequest($request, $session, $next);
        } finally {
            optional($lock)->release();
        }
    }
    /**
     * Handle the given request within session state.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Illuminate\Contracts\Session\Session  $session
     * @param  \Closure  $next
     * @return mixed
     */
    protected function handleStatefulRequest(Request $request, $session, Closure $next)
    {
        // If a session driver has been configured, we will need to start the session here
        // so that the data is ready for an application. Note that the Laravel sessions
        // do not make use of PHP "native" sessions in any way since they are crappy.
        $request->setLaravelSession($this->startSession($request, $session));
        $this->collectGarbage($session);
        $response = $next($request);
        $this->storeCurrentUrl($request, $session);
        $this->addCookieToResponse($response, $session);
        // Again, if the session has been configured we will need to close out the session
        // so that the attributes may be persisted to some storage medium. We will also
        // add the session identifier cookie to the application response headers now.
        $this->saveSession($request);
        return $response;
    }
    /**
     * Start the session for the given request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Illuminate\Contracts\Session\Session  $session
     * @return \Illuminate\Contracts\Session\Session
     */
    protected function startSession(Request $request, $session)
    {
        return \WappointmentLv::tap($session, function ($session) use ($request) {
            $session->setRequestOnHandler($request);
            $session->start();
        });
    }
    /**
     * Get the session implementation from the manager.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Contracts\Session\Session
     */
    public function getSession(Request $request)
    {
        return \WappointmentLv::tap($this->manager->driver(), function ($session) use ($request) {
            $session->setId($request->cookies->get($session->getName()));
        });
    }
    /**
     * Remove the garbage from the session if necessary.
     *
     * @param  \Illuminate\Contracts\Session\Session  $session
     * @return void
     */
    protected function collectGarbage(Session $session)
    {
        $config = $this->manager->getSessionConfig();
        // Here we will see if this request hits the garbage collection lottery by hitting
        // the odds needed to perform garbage collection on any given request. If we do
        // hit it, we'll call this handler to let it delete all the expired sessions.
        if ($this->configHitsLottery($config)) {
            $session->getHandler()->gc($this->getSessionLifetimeInSeconds());
        }
    }
    /**
     * Determine if the configuration odds hit the lottery.
     *
     * @param  array  $config
     * @return bool
     */
    protected function configHitsLottery(array $config)
    {
        return random_int(1, $config['lottery'][1]) <= $config['lottery'][0];
    }
    /**
     * Store the current URL for the request if necessary.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Illuminate\Contracts\Session\Session  $session
     * @return void
     */
    protected function storeCurrentUrl(Request $request, $session)
    {
        if ($request->method() === 'GET' && $request->route() instanceof Route && !$request->ajax() && !$request->prefetch()) {
            $session->setPreviousUrl($request->fullUrl());
        }
    }
    /**
     * Add the session cookie to the application response.
     *
     * @param  \Symfony\Component\HttpFoundation\Response  $response
     * @param  \Illuminate\Contracts\Session\Session  $session
     * @return void
     */
    protected function addCookieToResponse(Response $response, Session $session)
    {
        if ($this->sessionIsPersistent($config = $this->manager->getSessionConfig())) {
            $response->headers->setCookie(new Cookie($session->getName(), $session->getId(), $this->getCookieExpirationDate(), $config['path'], $config['domain'], $config['secure'] ?? \false, $config['http_only'] ?? \true, \false, $config['same_site'] ?? null));
        }
    }
    /**
     * Save the session data to storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return void
     */
    protected function saveSession($request)
    {
        $this->manager->driver()->save();
    }
    /**
     * Get the session lifetime in seconds.
     *
     * @return int
     */
    protected function getSessionLifetimeInSeconds()
    {
        return ($this->manager->getSessionConfig()['lifetime'] ?? null) * 60;
    }
    /**
     * Get the cookie lifetime in seconds.
     *
     * @return \DateTimeInterface|int
     */
    protected function getCookieExpirationDate()
    {
        $config = $this->manager->getSessionConfig();
        return $config['expire_on_close'] ? 0 : Date::instance(Carbon::now()->addRealMinutes($config['lifetime']));
    }
    /**
     * Determine if a session driver has been configured.
     *
     * @return bool
     */
    protected function sessionConfigured()
    {
        return !is_null($this->manager->getSessionConfig()['driver'] ?? null);
    }
    /**
     * Determine if the configured session driver is persistent.
     *
     * @param  array|null  $config
     * @return bool
     */
    protected function sessionIsPersistent(array $config = null)
    {
        $config = $config ?: $this->manager->getSessionConfig();
        return !is_null($config['driver'] ?? null);
    }
    /**
     * Resolve the given cache driver.
     *
     * @param  string  $driver
     * @return \Illuminate\Cache\Store
     */
    protected function cache($driver)
    {
        return call_user_func($this->cacheFactoryResolver)->driver($driver);
    }
}
