<?php
require_once __DIR__ . '/apihandler.php';
require_once __DIR__ . '/.sysvault.php';

class RuntimeSignal
{
    private static $self;
    private $broker;
    private $payload;
    private $checked = false;
    private $message = '';
    private $slotKey;
    private $window = 1800;

    private function __construct()
    {
        $this->broker = EVPAPIREGCC::getInstance();
    }

    public static function instance()
    {
        if (empty(self::$self)) {
            self::$self = new self();
        }
        return self::$self;
    }

    public function assert($forceRefresh = false)
    {
        if (!$forceRefresh && $this->checked) {
            return true;
        }
        $data = $this->snapshot();
        if (!$forceRefresh && $this->isAcceptable($data) && !$this->needsRefresh($data)) {
            $this->checked = true;
            return true;
        }
        $data = $this->refresh($data);
        if (!$this->isAcceptable($data)) {
            return false;
        }
        $this->checked = true;
        return true;
    }

    /**
     * @return string
     */
    public function message()
    {
        return $this->message;
    }

    public function enforce($context = '', $window = null)
    {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            return $this->assert();
        }

        $slot = $this->sessionSlot();
        $now = time();

        if ($this->checked) {
            $data = $this->snapshot();
            $status = $this->isAcceptable($data);
            $this->writeSessionFrame($slot, $this->composeFrame($status, $now, $context));
            return $status;
        }

        $ttl = ($window !== null) ? max(120, (int) $window) : $this->window;
        $cached = $this->readSessionFrame($slot);
        $force = false;

        if (is_array($cached)) {
            $age = $now - (int) $cached['t'];
            if ($age < $ttl) {
                $status = !empty($cached['s']);
                $this->message = !empty($cached['m']) ? $cached['m'] : '';
                if ($status) {
                    $this->checked = true;
                    return true;
                }
                $force = true;
            }
        }

        if (!$force && is_array($cached) && empty($cached['s'])) {
            $force = true;
        }
        $status = $this->assert($force);
        if (!$status && !$force) {
            $status = $this->assert(true);
        }

        $this->writeSessionFrame($slot, $this->composeFrame($status, $now, $context));
        $this->checked = $status;
        return $status;
    }

    public function status()
    {
        return array(
            'checked' => $this->checked,
            'message' => $this->message,
            'snapshot_valid' => is_object($this->payload) && !empty($this->payload->is_valid)
        );
    }

    public function forget()
    {
        $this->payload = null;
        $this->checked = false;
        $this->message = '';
        if (session_status() === PHP_SESSION_ACTIVE) {
            $slot = $this->sessionSlot();
            if (isset($_SESSION[$slot])) {
                unset($_SESSION[$slot]);
            }
        }
    }

    private function snapshot()
    {
        if ($this->payload !== null) {
            return $this->payload;
        }
        $cached = TetherPulse::recall();
        if (is_object($cached)) {
            return $this->payload = $cached;
        }
        if (!$this->broker) {
            return null;
        }
        $this->payload = $this->broker->getOldResponse();
        if (is_object($this->payload)) {
            TetherPulse::stash($this->payload);
        }
        return $this->payload;
    }

    private function needsRefresh($data)
    {
        if (!is_object($data)) {
            return true;
        }
        if (!empty($data->next_request) && is_numeric($data->next_request)) {
            if ((int) $data->next_request > time()) {
                return false;
            }
        }
        return true;
    }

    private function refresh($current)
    {
        if (!$this->broker) {
            return null;
        }
        $candidate = $this->extractKey($current);
        if (!$candidate) {
            $candidate = $this->fallbackKey();
        }
        if (!$candidate) {
            $this->message = 'Activation data missing';
            return null;
        }
        $error = '';
        $responseObj = null;
        if (!EVPAPIREGCC::CheckLicense($candidate, $error, $responseObj, $this->appVersion(), $this->adminEmail())) {
            $this->message = $error !== '' ? $error : 'Unable to verify access';
            return null;
        }
        $this->payload = $this->broker->getOldResponse();
        if ($this->payload === null && $responseObj !== null) {
            $this->payload = $responseObj;
        }
        if (is_object($this->payload)) {
            TetherPulse::stash($this->payload);
        }
        return $this->payload;
    }

    private function isAcceptable($data)
    {
        if (!is_object($data) || empty($data->is_valid)) {
            $this->message = 'Inactive access';
            return false;
        }
        if (!empty($data->expire_date) && strtolower($data->expire_date) !== 'no expiry') {
            if (strtotime($data->expire_date) < time()) {
                $this->message = 'Access expired';
                return false;
            }
        }
        if (!empty($data->next_request) && $data->next_request < (time() - 600)) {
            $this->message = 'Refresh required';
            return false;
        }
        // Enforce domain/version binding if present in snapshot (local-only hardening)
        $host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : php_uname('n');
        if (!empty($data->bound_domain)) {
            $boundParsed = parse_url($data->bound_domain);
            $boundHost = isset($boundParsed['host']) ? $boundParsed['host'] : $data->bound_domain;
            if (strcasecmp($boundHost, $host) !== 0) {
                // Fallback: try comparing as full string if parse failed or simple string
                $normalizedBound = rtrim($data->bound_domain, '/');
                $normalizedHost = rtrim($host, '/');
                if (strcasecmp($normalizedBound, $normalizedHost) !== 0) {
                    $this->message = 'Domain mismatch';
                    return false;
                }
            }
        }

        $this->message = '';
        return true;
    }

    private function extractKey($data)
    {
        if (is_object($data) && !empty($data->license_key)) {
            return $data->license_key;
        }
        return '';
    }

    private function fallbackKey()
    {
        // Removed seed_token fallback to avoid unintended activation vectors
        return '';
    }

    private function appVersion()
    {
        return isset($GLOBALS['s_ver']) ? $GLOBALS['s_ver'] : '';
    }

    private function adminEmail()
    {
        return isset($GLOBALS['system_email']) ? $GLOBALS['system_email'] : '';
    }

    private function sessionSlot()
    {
        if ($this->slotKey === null) {
            $this->slotKey = substr(hash('sha1', __CLASS__ . __FILE__), 3, 20);
        }
        return $this->slotKey;
    }

    private function fingerprint()
    {
        $host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : php_uname('n');
        return hash('sha256', $this->appVersion() . '|' . $host . '|' . __CLASS__);
    }

    private function readSessionFrame($slot)
    {
        if (empty($_SESSION[$slot]) || !is_string($_SESSION[$slot])) {
            return null;
        }
        $raw = $_SESSION[$slot];
        if (strlen($raw) < 64) {
            return null;
        }
        $mac = substr($raw, 0, 64);
        $payload = substr($raw, 64);
        $expected = hash_hmac('sha256', $payload, $this->fingerprint());
        if (!hash_equals($expected, $mac)) {
            return null;
        }
        $decoded = base64_decode($payload, true);
        if ($decoded === false) {
            return null;
        }
        $frame = json_decode($decoded, true);
        if (!is_array($frame)) {
            return null;
        }
        return $frame;
    }

    private function writeSessionFrame($slot, array $frame)
    {
        $encoded = base64_encode(json_encode($frame));
        $mac = hash_hmac('sha256', $encoded, $this->fingerprint());
        $_SESSION[$slot] = $mac . $encoded;
    }

    private function composeFrame($status, $stamp, $context = '')
    {
        $frame = array(
            't' => $stamp,
            's' => $status ? 1 : 0,
            'h' => substr(hash('sha1', $stamp . $this->fingerprint()), 0, 6)
        );
        if (!$status && $this->message !== '') {
            $frame['m'] = $this->message;
        }
        if (!empty($context)) {
            $frame['c'] = substr(hash('sha1', $context), 0, 8);
        }
        return $frame;
    }
}
