<?php

namespace WPD\Security;

use WPD\Application\Config;

final class AccessGuard
{
    private $config;
    private $activation;
    private $limiter;
    private $clock;
    private $mode;
    private $address;

    public function __construct(Config $config, ActivationStore $activation, AttemptLimiter $limiter, Clock $clock, $mode, $address)
    {
        $this->config = $config;
        $this->activation = $activation;
        $this->limiter = $limiter;
        $this->clock = $clock;
        $this->mode = (string) $mode;
        $this->address = (string) $address;
    }

    public function accessAvailable()
    {
        $inspection = $this->activation->inspect($this->mode);
        return in_array($inspection['status'], array(ActivationStore::MISSING, ActivationStore::VALID), true);
    }

    public function isAuthenticated()
    {
        $inspection = $this->activation->inspect($this->mode);
        if ($inspection['status'] !== ActivationStore::VALID || empty($inspection['state']['activation_id'])) {
            $this->invalidateSession();
            return false;
        }
        if (empty($_SESSION['wpd_authenticated'])
            || empty($_SESSION['wpd_activation_id'])
            || empty($_SESSION['wpd_created_at'])
            || empty($_SESSION['wpd_last_activity'])
            || !hash_equals((string) $inspection['state']['activation_id'], (string) $_SESSION['wpd_activation_id'])) {
            return false;
        }

        $now = $this->clock->now();
        if ($now >= (int) $inspection['state']['expires_at']
            || $now - (int) $_SESSION['wpd_last_activity'] > (int) $this->config->get('session_timeout', 1800)) {
            $this->invalidateSession();
            return false;
        }

        $_SESSION['wpd_last_activity'] = $now;
        return true;
    }

    public function attempt($token)
    {
        if ($this->limiter->isBlocked($this->address)) {
            return false;
        }

        if (!is_string($token) || !hash_equals((string) $this->config->get('access_token'), $token)) {
            $this->limiter->recordFailure($this->address);
            return false;
        }

        $inspection = $this->activation->inspect($this->mode);
        if ($inspection['status'] === ActivationStore::MISSING) {
            $state = $this->activation->activate($this->mode);
        } elseif ($inspection['status'] === ActivationStore::VALID) {
            $state = $inspection['state'];
        } else {
            return false;
        }

        if ($state === null || !$this->limiter->clearAddress($this->address)) {
            return false;
        }

        session_regenerate_id(true);
        $now = $this->clock->now();
        $_SESSION['wpd_authenticated'] = true;
        $_SESSION['wpd_activation_id'] = $state['activation_id'];
        $_SESSION['wpd_created_at'] = $now;
        $_SESSION['wpd_last_activity'] = $now;
        return true;
    }

    public function logout()
    {
        $_SESSION = array();
        if (ini_get('session.use_cookies')) {
            $params = session_get_cookie_params();
            setcookie(session_name(), '', time() - 42000, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
        }
        session_destroy();
    }

    private function invalidateSession()
    {
        unset(
            $_SESSION['wpd_authenticated'],
            $_SESSION['wpd_activation_id'],
            $_SESSION['wpd_created_at'],
            $_SESSION['wpd_last_activity'],
            $_SESSION['wpd_scan'],
            $_SESSION['wpd_manual_database'],
            $_SESSION['wpd_csrf']
        );
        if (session_status() === PHP_SESSION_ACTIVE) {
            session_regenerate_id(true);
        }
    }
}
