<?php

declare(strict_types=1);

namespace App\Domain\License\Services;

use Carbon\Carbon;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\File;

/**
 * LicenseManager — JWT cache (file + RAM) backed license state.
 *
 * Tasarım:
 * - JWT dosyada saklanır — L3: machine_id'ye bağlı hashed path + envelope wrapping
 *   prod:  /var/lib/.onx-cache/<12-hex>/.<12-hex>.dat
 *   dev:   storage/app/.cache/<12-hex>/.<12-hex>.dat
 *   Legacy path (storage/app/onoxsoft/license.jwt) okunup migrate edilir.
 * - RAM cache (60s TTL) — sıcak path için disk I/O atlanır
 * - getValidClaims() önce IntegrityVerifier::verify() çalıştırır (manifest hash check)
 *   sonra JwtVerifier ile imza + exp kontrol eder
 * - Integrity mismatch → KillSwitch + null claims (sessiz reddetme)
 * - storeJwt() yeni JWT yazılınca KillSwitch otomatik deactivate edilir
 * - isDevMode() localhost/local/testing env'de tüm featureları açar
 *
 * Legacy compat: FEATURE_* + TIER_* constants ve hasFeature/isValid/accountLimit/isDevMode
 * method isimleri korundu (eski callerların bozulmaması için).
 */
class LicenseManager
{
    // Feature constants (callerlar bunlara referans veriyor — KORUNDU)
    public const FEATURE_MAIL_SERVER = 'mail_server';

    public const FEATURE_PLUGINS = 'plugins';

    public const FEATURE_API = 'api';

    public const FEATURE_AI_ASSISTANT = 'ai_assistant';

    public const FEATURE_WHMCS_PLUGIN = 'whmcs_plugin';

    public const FEATURE_AUTOSSL = 'autossl';

    public const FEATURE_CLUSTER = 'cluster';

    public const FEATURE_RESELLER = 'reseller';

    public const FEATURE_MIGRATION = 'cpanel_migration';

    // Tier constants (legacy compat)
    public const TIER_TRIAL = 'trial';

    public const TIER_LITE = 'lite';

    public const TIER_PRO = 'pro';

    public const TIER_UNLIMITED = 'unlimited';

    public const TIER_ENTERPRISE = 'enterprise';

    public const TIER_DEV = 'dev';

    private const CACHE_KEY = 'onoxsoft.license.jwt';

    private const CACHE_TTL_SECONDS = 60;

    public function __construct(
        private JwtVerifier $verifier,
        private KillSwitch $killSwitch,
        private ?IntegrityVerifier $integrity = null,
        private ?MachineFingerprint $fingerprint = null,
    ) {}

    /**
     * License geçerli mi?
     * Önce JWT'ye bakar; varsa onu kullanır. Yoksa dev mode'da true (geliştirme kolaylığı için),
     * production'da false. Bu sayede dev'de bile gerçek lisans testi yapılabilir
     * (JWT cache'lendiyse Pro tier gözükür, silinince satın al sayfasına yönlendirir).
     */
    public function isValid(): bool
    {
        if ($this->getValidClaims() !== null) {
            return true;
        }

        // JWT yoksa: dev mode'da geliştirme kolaylığı için lisans yokmuş gibi davranır.
        // (Önceden true dönerdi → her zaman bypass. Artık false → /lisans/satin-al'a yönlendirir.)
        return false;
    }

    public function getValidClaims(): ?array
    {
        // L3 anti-crack: integrity check fails → JWT'yi geçersiz say + KillSwitch
        // Integrity verifier yoksa (legacy/test bootstrap) skip
        if ($this->integrity && ! $this->integrity->verify()) {
            // Sessizce reddet — saldırgan tam neden olduğunu öğrenmesin
            // Fire-and-forget crack report License Server'a (heartbeat zaten yapacak)
            $this->reportTamper();
            return null;
        }

        $jwt = $this->loadJwt();
        if (! $jwt) {
            return null;
        }

        return $this->verifier->verify($jwt);
    }

    /**
     * Anti-tamper: integrity mismatch → KillSwitch + sessiz log.
     * Bir cracker bu metodu da NOP'a çevirmek isterse, IntegrityVerifier kendi
     * dosyasının hash'ini de doğruluyor (self-check).
     */
    private function reportTamper(): void
    {
        try {
            if (! $this->killSwitch->isActive()) {
                // 'integrity_mismatch' anahtarı sebep, ama log'da görünür değil
                // (UI sadece "Mail servisleri durduruldu" gösterir)
                $this->killSwitch->activate('integrity_mismatch', 'system_health');
            }
        } catch (\Throwable) {
            // Bozulan KillSwitch yine de panel'i durdursun — JWT null döndük
        }
    }

    public function tier(): ?string
    {
        $c = $this->getValidClaims();
        if ($c) {
            return $c['license']['tier'] ?? null;
        }

        // JWT yoksa dev mode'da TIER_DEV (UI'da "Dev" göster)
        if ($this->isDevMode()) {
            return self::TIER_DEV;
        }

        return null;
    }

    public function features(): array
    {
        $c = $this->getValidClaims();
        if ($c) {
            return $c['license']['features'] ?? [];
        }

        // JWT yoksa dev mode'da tüm featurelar açık
        if ($this->isDevMode()) {
            return ['*'];
        }

        return [];
    }

    public function accountLimit(): int
    {
        $c = $this->getValidClaims();
        if ($c) {
            $limit = $c['license']['account_limit'] ?? 0;

            return $limit > 0 ? (int) $limit : PHP_INT_MAX;
        }

        if ($this->isDevMode()) {
            return PHP_INT_MAX;
        }

        return 0;
    }

    public function hasFeature(string $feature): bool
    {
        // Önce admin override'a bak (license_feature_overrides tablosu)
        // Admin "mail_server'ı geçici kapat" diyebilir — bu lisansta varsa bile false döner
        $override = $this->featureOverride($feature);
        if ($override !== null) {
            return $override;
        }

        $f = $this->features();
        if (! empty($f)) {
            return in_array('*', $f, true) || in_array($feature, $f, true);
        }

        // JWT yoksa dev mode'da true (geliştirme)
        return $this->isDevMode();
    }

    /**
     * Admin override durumu — null = override yok, bool = override değeri
     */
    private function featureOverride(string $feature): ?bool
    {
        try {
            if (! \Illuminate\Support\Facades\Schema::hasTable('license_feature_overrides')) {
                return null;
            }
            $row = \Illuminate\Support\Facades\DB::table('license_feature_overrides')
                ->where('feature', $feature)->first();

            return $row ? (bool) $row->enabled : null;
        } catch (\Throwable) {
            return null;
        }
    }

    /**
     * Tier'a göre tüm lisanslı feature'ları ve admin override durumunu döndür.
     * Admin Lisans Yönetimi sayfasında kullanılır.
     */
    public function featureMatrix(): array
    {
        $allFeatures = [
            self::FEATURE_API => 'API Erişimi',
            self::FEATURE_AUTOSSL => 'AutoSSL (Let\'s Encrypt)',
            self::FEATURE_PLUGINS => 'Plugin Marketplace',
            self::FEATURE_MAIL_SERVER => 'Mail Server (Postfix/Dovecot)',
            self::FEATURE_WHMCS_PLUGIN => 'WHMCS Plugin',
            self::FEATURE_AI_ASSISTANT => 'AI Asistan',
            self::FEATURE_MIGRATION => 'cPanel Migration',
            self::FEATURE_CLUSTER => 'Multi-Server Cluster',
            self::FEATURE_RESELLER => 'Reseller Layer',
        ];

        $licensed = $this->features();
        $hasAll = in_array('*', $licensed, true);

        $matrix = [];
        foreach ($allFeatures as $key => $label) {
            $inLicense = $hasAll || in_array($key, $licensed, true);
            $override = $this->featureOverride($key);
            $matrix[] = [
                'feature' => $key,
                'label' => $label,
                'in_license' => $inLicense,
                'override' => $override,                 // null|true|false
                'effective' => $override ?? $inLicense,  // gerçek runtime durum
            ];
        }

        return $matrix;
    }

    public function expiresAt(): ?Carbon
    {
        $c = $this->getValidClaims();
        if (! $c || empty($c['license']['expires_at'])) {
            return null;
        }

        return Carbon::parse($c['license']['expires_at']);
    }

    /**
     * Lisans bitişine kaç gün kaldı? (null = JWT yok veya expires_at yok)
     * Negatif = expired (geçmişte).
     */
    public function daysUntilExpiry(): ?int
    {
        $exp = $this->expiresAt();
        if (! $exp) {
            return null;
        }

        return (int) now()->startOfDay()->diffInDays($exp->startOfDay(), false);
    }

    /**
     * Lisans yakında bitecek mi? (14 gün altı → true, expired da true)
     * UI'da bildirim göstermek için kullanılır.
     */
    public function expiresSoon(int $thresholdDays = 14): bool
    {
        $days = $this->daysUntilExpiry();
        if ($days === null) {
            return false;
        }

        return $days <= $thresholdDays;
    }

    public function customerEmail(): ?string
    {
        $c = $this->getValidClaims();

        return $c['license']['customer_email'] ?? null;
    }

    public function uuid(): ?string
    {
        $c = $this->getValidClaims();

        return $c['sub'] ?? null;
    }

    public function boundIp(): ?string
    {
        $c = $this->getValidClaims();

        return $c['license']['bound_ip'] ?? null;
    }

    /**
     * Store new JWT (called after activate/heartbeat/transfer-ip).
     * Auto-deactivates KillSwitch if JWT is valid.
     *
     * L3: Envelope wrapping — raw JWT yerine maskelenmiş veri yazılır.
     * Dosyayı cat'leyen biri JWT formatını göremez (random bytes gibi görünür).
     */
    public function storeJwt(string $jwt): void
    {
        // Eski path'leri temizle (legacy migration)
        $this->cleanupLegacyPaths();

        $path = $this->jwtPath();
        File::ensureDirectoryExists(dirname($path), 0700);
        File::put($path, $this->wrapEnvelope($jwt));
        @chmod($path, 0600);

        Cache::put(self::CACHE_KEY, $jwt, self::CACHE_TTL_SECONDS);

        // Yeni JWT geçerli mi? Eğer öyleyse KillSwitch'i deactivate et
        if ($this->verifier->verify($jwt) !== null && $this->killSwitch->isActive()) {
            $this->killSwitch->deactivate();
        }
    }

    public function clearJwt(): void
    {
        Cache::forget(self::CACHE_KEY);
        $path = $this->jwtPath();
        if (file_exists($path)) {
            @unlink($path);
        }
        $this->cleanupLegacyPaths();
    }

    public function isDevMode(): bool
    {
        $host = parse_url((string) config('app.url'), PHP_URL_HOST);

        return in_array($host, ['localhost', '127.0.0.1', '[::1]'], true)
            || app()->environment('local', 'testing');
    }

    private function loadJwt(): ?string
    {
        return Cache::remember(self::CACHE_KEY, self::CACHE_TTL_SECONDS, function () {
            // Önce yeni hashed path
            $path = $this->jwtPath();
            if (is_readable($path)) {
                $raw = @file_get_contents($path);
                $unwrapped = $raw ? $this->unwrapEnvelope($raw) : null;
                if ($unwrapped) {
                    return $unwrapped;
                }
            }

            // Legacy: eski path'lerden migration
            foreach ($this->legacyJwtPaths() as $legacy) {
                if (is_readable($legacy)) {
                    $jwt = trim(@file_get_contents($legacy) ?: '');
                    if ($jwt) {
                        // Yeni envelope-li path'e taşı
                        try {
                            File::ensureDirectoryExists(dirname($path), 0700);
                            File::put($path, $this->wrapEnvelope($jwt));
                            @chmod($path, 0600);
                            @unlink($legacy);
                        } catch (\Throwable) {
                            // Migration başarısız olsa bile JWT'yi return edelim
                        }
                        return $jwt;
                    }
                }
            }

            return null;
        });
    }

    /**
     * JWT path — fingerprint hash bazlı obscure path.
     * /etc/onoxsoft/license.jwt yerine: /var/lib/.onx-cache/.<sha-snippet>.dat
     * Cracker önce nereye baktığını bilmek zorunda.
     */
    private function jwtPath(): string
    {
        $machineId = $this->fingerprint?->machineId() ?? 'fallback';
        $tag = substr(hash('sha256', $machineId . '|onox-jwt-path-v1'), 0, 24);
        $fileName = '.' . substr($tag, 0, 12) . '.dat';

        if (PHP_OS_FAMILY === 'Linux' && app()->environment('production')) {
            $dir = '/var/lib/.onx-cache/' . substr($tag, 12);
            return $dir . '/' . $fileName;
        }

        return storage_path('app/.cache/' . substr($tag, 12) . '/' . $fileName);
    }

    /**
     * Eski legacy path'ler — migration için okunur, taşıma sonrası silinir.
     */
    private function legacyJwtPaths(): array
    {
        return [
            '/etc/onoxsoft/license.jwt',
            storage_path('app/onoxsoft/license.jwt'),
        ];
    }

    private function cleanupLegacyPaths(): void
    {
        foreach ($this->legacyJwtPaths() as $legacy) {
            if (file_exists($legacy)) {
                @unlink($legacy);
            }
        }
    }

    /**
     * Envelope wrap — JWT'yi pure-binary görünüme çevirir.
     * Format: [magic 4b][len 4b BE][payload][crc 4b]
     * payload = JWT XOR'lanmış key stream (key = sha256(machine_id))
     * Bu PHP-side reversible — gerçek anti-crack değil, "cat" görünümünü bozar.
     * Cracker reverse engineer ederse açabilir ama bunu yapmak için bu kodu okumalı.
     */
    private function wrapEnvelope(string $jwt): string
    {
        $magic = "\x4F\x4E\x58\x01"; // ONX\x01
        $payload = $this->xorMask($jwt);
        $len = pack('N', strlen($payload));
        $crc = pack('N', crc32($payload));
        return $magic . $len . $payload . $crc;
    }

    private function unwrapEnvelope(string $raw): ?string
    {
        if (strlen($raw) < 12) {
            // Belki eski raw JWT formatı — geriye uyumluluk
            $maybe = trim($raw);
            return $maybe ?: null;
        }
        $magic = substr($raw, 0, 4);
        if ($magic !== "\x4F\x4E\x58\x01") {
            // Magic eşleşmiyor — eski format raw JWT
            $maybe = trim($raw);
            return $maybe ?: null;
        }
        $lenBytes = substr($raw, 4, 4);
        $len = unpack('N', $lenBytes)[1] ?? 0;
        if ($len <= 0 || strlen($raw) < 8 + $len + 4) {
            return null;
        }
        $payload = substr($raw, 8, $len);
        $crcBytes = substr($raw, 8 + $len, 4);
        $crc = unpack('N', $crcBytes)[1] ?? 0;
        if (crc32($payload) !== $crc) {
            return null;
        }
        return $this->xorMask($payload);
    }

    /**
     * XOR mask — key stream = sha256(machine_id) tekrarı.
     * Symmetric: aynı fonksiyon hem wrap hem unwrap.
     */
    private function xorMask(string $data): string
    {
        $key = hash('sha256', ($this->fingerprint?->machineId() ?? 'fallback') . '|onx-mask-v1', true);
        $keyLen = strlen($key);
        $out = '';
        $dataLen = strlen($data);
        for ($i = 0; $i < $dataLen; $i++) {
            $out .= $data[$i] ^ $key[$i % $keyLen];
        }
        return $out;
    }
}
