<?php

declare(strict_types=1);



class LicenseGuard
{
    private const TOKEN_GRACE_AFTER_EXP = 14 * 86400;  
    private const WRONG_DOMAIN_GRACE    = 7 * 86400;   
    private const REVALIDATE_INTERVAL   = 6 * 3600;    
    private const VENDOR_HOST           = 'tubepress.io';  

    private static ?array $state  = null;   
    private static ?array $tokens = null;   
    private static array  $allowCache = [];  

    public static function registerCron(): void
    {
        CronManager::register('license_revalidate', self::REVALIDATE_INTERVAL, [self::class, 'revalidate']);
    }

     
    public static function host(): string
    {
        $d = Domain::registrable((string) ($_SERVER['HTTP_HOST'] ?? ''));
        if ($d === '' || $d === 'localhost' || filter_var($d, FILTER_VALIDATE_IP)) {
            return '';
        }
        return $d;
    }

    

    public static function isFirstParty(): bool
    {
        return Domain::registrable((string) ($_SERVER['HTTP_HOST'] ?? '')) === self::VENDOR_HOST;
    }

    

    public static function isAllowed(string $type, string $slug): bool
    {
        $key = $type . ':' . $slug;
        if (array_key_exists($key, self::$allowCache)) {
            return self::$allowCache[$key];
        }
        if (self::isFirstParty()) {
            return self::$allowCache[$key] = true;  
        }
        try {
            $allow = self::evaluate($type, $slug);
        } catch (\Throwable $e) {
            error_log('LicenseGuard::isAllowed ' . $key . ': ' . $e->getMessage());
            $allow = true;  
        }
        return self::$allowCache[$key] = $allow;
    }

    private static function evaluate(string $type, string $slug): bool
    {
        $key = $type . ':' . $slug;
        $host = self::host();

        $tok = self::tokens()[$key] ?? '';
        if ($tok !== '') {
            $p = LicenseToken::verify($tok);
            if (is_array($p) && ($p['s'] ?? '') === $slug && ($p['t'] ?? '') === $type) {
                $domainOk = ($host === '' || ($p['d'] ?? '') === $host);
                $freshEnough = ((int) ($p['exp'] ?? 0) + self::TOKEN_GRACE_AFTER_EXP) > time();
                if ($domainOk && $freshEnough) {
                    return true;  
                }
            }
        }

         
        $st = self::state()[$key] ?? null;
        if ($st && ($st['status'] ?? '') === 'blocked') {
            return false;
        }
        return true;
    }

    

    public static function revalidate(): void
    {
        if (self::isFirstParty()) {
            return;  
        }
        $host = self::host();
        if ($host === '') {
            return;  
        }
        $items = self::activeItems();
        if (!$items) {
            return;
        }

        self::state();
        self::tokens();  

        $results = null;
        if (CatalogueClient::isConfigured()) {
            $resp = CatalogueClient::licenseStatus($host, array_map(fn($i) => ['type' => $i[0], 'slug' => $i[1]], $items));
            if (is_array($resp) && empty($resp['_error']) && isset($resp['results']) && is_array($resp['results'])) {
                $results = $resp['results'];
            } else {
                return;  
            }
        }

        foreach ($items as [$type, $slug]) {
            $key = $type . ':' . $slug;
            if ($results !== null) {
                self::applyVerdict($type, $slug, $key, $host, $results[$key] ?? ['licensed' => true, 'reason' => 'unknown']);
            } else {
                self::applyOffline($key, $host);  
            }
        }
        self::persist();
    }

    private static function applyVerdict(string $type, string $slug, string $key, string $host, array $r): void
    {
        if (!empty($r['licensed'])) {
            if (!empty($r['token'])) {
                self::$tokens[$key] = (string) $r['token'];
            }
            self::$state[$key] = ['status' => 'ok', 'reason' => (string) ($r['reason'] ?? ''), 'checked' => time()];
            return;
        }

        $reason = (string) ($r['reason'] ?? '');
        if ($reason === 'not_bound' && !empty($r['can_bind'])) {
             
            $a = CatalogueClient::activateSite($type, $slug, $host);
            if (is_array($a) && !empty($a['license_token'])) {
                self::$tokens[$key] = (string) $a['license_token'];
                self::$state[$key] = ['status' => 'ok', 'reason' => 'rebound', 'checked' => time()];
                return;
            }
        }

        unset(self::$tokens[$key]);  
        self::markNegative($key, $reason, $r['bound_domains'] ?? []);
    }

    private static function applyOffline(string $key, string $host): void
    {
        $tok = self::$tokens[$key] ?? '';
        if ($tok === '') {
             
            self::$state[$key] = ['status' => 'ok', 'reason' => 'unconfigured', 'checked' => time()];
            return;
        }
        $p = LicenseToken::verify($tok);
        if (is_array($p) && ($p['d'] ?? '') === $host && ((int) ($p['exp'] ?? 0) + self::TOKEN_GRACE_AFTER_EXP) > time()) {
            self::$state[$key] = ['status' => 'ok', 'reason' => 'offline_token', 'checked' => time()];
        } else {
            self::markNegative($key, 'offline_unverified', []);  
        }
    }

     
    private static function markNegative(string $key, string $reason, array $bound): void
    {
        $st   = self::$state[$key] ?? null;
        $prev = $st['status'] ?? '';

         
         
         
         
         
        if ($prev !== 'grace' && $prev !== 'blocked' && empty($st['strike'])) {
            self::$state[$key] = [
                'status'  => 'unknown',  
                'reason'  => 'recheck:' . $reason,
                'strike'  => 1,
                'checked' => time(),
            ];
            return;
        }

        $since = ($prev === 'grace') ? (int) ($st['since'] ?? time()) : time();
        $status = ((time() - $since) >= self::WRONG_DOMAIN_GRACE) ? 'blocked' : 'grace';
        self::$state[$key] = [
            'status'  => $status,
            'reason'  => $reason,
            'since'   => $since,
            'checked' => time(),
            'bound'   => array_values($bound),
        ];
    }

    private static function activeItems(): array
    {
        $items = [];
        try {
            foreach (Database::fetchAll('SELECT slug FROM plugins WHERE is_active = 1') as $row) {
                $items[] = ['plugin', (string) $row['slug']];
            }
        } catch (\Throwable $e) {
        }
        $theme = ThemeManager::active();
        if ($theme !== '' && $theme !== 'simply') {
            $items[] = ['theme', $theme];
        }
        return $items;
    }

     
    public static function adminNotice(): ?array
    {
        if (self::isFirstParty()) {
            return null;  
        }
        $bad = [];
        foreach (self::state() as $key => $st) {
            $status = $st['status'] ?? '';
            if ($status === 'grace' || $status === 'blocked') {
                [$type, $slug] = array_pad(explode(':', (string) $key, 2), 2, '');
                $bad[] = [
                    'type'   => $type,
                    'slug'   => $slug,
                    'status' => $status,
                    'reason' => $st['reason'] ?? '',
                    'bound'  => $st['bound'] ?? [],
                ];
            }
        }
        return $bad ?: null;
    }

     
    private static function state(): array
    {
        if (self::$state === null) {
            self::$state = json_decode((string) Setting::get('license_state', ''), true) ?: [];
        }
        return self::$state;
    }

    private static function tokens(): array
    {
        if (self::$tokens === null) {
            self::$tokens = json_decode((string) Setting::get('license_tokens', ''), true) ?: [];
        }
        return self::$tokens;
    }

    private static function persist(): void
    {
        Setting::set('license_state', json_encode(self::$state ?? []));
        Setting::set('license_tokens', json_encode(self::$tokens ?? []));
        self::$allowCache = [];  
    }
}
