<?php

declare(strict_types=1);

namespace App\Domain\License\Services;

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;

/**
 * IntegrityVerifier — Lisans-kritik dosyaların SHA256 hash'lerini doğrular.
 *
 * Strateji: Tek bir bypass kapısı yok — bir cracker LicenseManager'ı patch'lemek isterse
 * bu sınıf hash uyuşmazlığını yakalar ve KillSwitch'i tetikler. Bu sınıfı patch'lemek
 * isterse, sınıfın kendi hash'i manifest'te yer alır ve _self_check_ ile kontrol edilir.
 *
 * Manifest:
 *   config/onoxsoft/integrity.php → ['file/path.php' => 'sha256_hex']
 *
 * Manifest yenileme:
 *   php artisan onox:integrity:rebuild
 *   (Sadece geliştirici makinesinde, deploy sonrası.)
 *
 * Üretimde:
 *   Her LicenseManager::getValidClaims() çağrısında verify() çalışır (60s cache).
 *   Hash uyuşmazlığı → KillSwitch activate + License Server'a crackReport.
 *
 * NOT: Bu sınıfın kendisi de manifest'e dahildir. Bypass denemesi için bu dosyayı
 * silmek/değiştirmek de uyuşmazlık üretir (LicenseManager Container'dan çözer,
 * sınıf yoksa Exception, KillSwitch tetiklenir).
 */
class IntegrityVerifier
{
    private const CACHE_KEY = '_sys_chk';
    private const CACHE_TTL = 60;
    private const TOL_BYTES = 0;  // 0 = tam eşleşme; SCM line-ending farklılığı için >0 yapılabilir

    /**
     * Manifest yüklenir, her dosya hash'i karşılaştırılır.
     * @return bool true = tüm dosyalar uyumlu, false = en az 1 mismatch
     */
    public function verify(): bool
    {
        // Cache hit
        $cached = Cache::get(self::CACHE_KEY);
        if ($cached !== null) {
            return (bool) $cached;
        }

        $manifest = $this->loadManifest();
        if (empty($manifest)) {
            // Manifest yoksa: development ortamı varsayılır, integrity skip
            Cache::put(self::CACHE_KEY, true, self::CACHE_TTL);
            return true;
        }

        $allOk = true;
        $mismatches = [];

        foreach ($manifest as $relativePath => $expectedHash) {
            $fullPath = base_path($relativePath);
            if (! is_readable($fullPath)) {
                $allOk = false;
                $mismatches[] = "$relativePath:missing";
                continue;
            }

            $actualHash = $this->computeHash($fullPath);
            if (! hash_equals($expectedHash, $actualHash)) {
                $allOk = false;
                $mismatches[] = "$relativePath:tampered";
            }
        }

        // Self-check: bu sınıfın kendisi de manifest'te
        $selfPath = (new \ReflectionClass(self::class))->getFileName();
        if ($selfPath && is_readable($selfPath)) {
            $selfRelative = str_replace(base_path() . DIRECTORY_SEPARATOR, '', $selfPath);
            $selfRelative = str_replace(DIRECTORY_SEPARATOR, '/', $selfRelative);
            if (isset($manifest[$selfRelative])) {
                $actualSelfHash = $this->computeHash($selfPath);
                if (! hash_equals($manifest[$selfRelative], $actualSelfHash)) {
                    $allOk = false;
                    $mismatches[] = "$selfRelative:self-tampered";
                }
            }
        }

        Cache::put(self::CACHE_KEY, $allOk, self::CACHE_TTL);

        if (! $allOk) {
            Log::warning('Integrity check failed', ['mismatches' => $mismatches]);
        }

        return $allOk;
    }

    /**
     * Manifest oluştur — admin'in artisan komutuyla çağırdığı.
     * Critical files listesindeki tüm dosyaların hash'lerini döner.
     */
    public function buildManifest(): array
    {
        $manifest = [];
        foreach ($this->criticalFiles() as $relativePath) {
            $fullPath = base_path($relativePath);
            if (! is_readable($fullPath)) {
                continue;
            }
            $manifest[$relativePath] = $this->computeHash($fullPath);
        }

        return $manifest;
    }

    /**
     * Manifest'i config dosyasına yaz — sadece dev environment'da.
     */
    public function writeManifest(array $manifest): bool
    {
        $configPath = base_path('config/onoxsoft/integrity.php');
        $dir = dirname($configPath);
        if (! is_dir($dir)) {
            @mkdir($dir, 0755, true);
        }

        $php = "<?php\n\n";
        $php .= "/**\n";
        $php .= " * Onoxsoft license integrity manifest — AUTO-GENERATED.\n";
        $php .= " * Bu dosyayı manuel düzenlemeyin. Yeniden üretmek için:\n";
        $php .= " *   php artisan onox:integrity:rebuild\n";
        $php .= " * Üretim: deploy sonrası bir kez çalıştırılmalı.\n";
        $php .= " */\n\n";
        $php .= "return " . var_export($manifest, true) . ";\n";

        return file_put_contents($configPath, $php) !== false;
    }

    /**
     * Manifest'i config'den oku. Yoksa boş array.
     */
    private function loadManifest(): array
    {
        $configPath = base_path('config/onoxsoft/integrity.php');
        if (! is_readable($configPath)) {
            return [];
        }

        try {
            $data = require $configPath;
            return is_array($data) ? $data : [];
        } catch (\Throwable) {
            return [];
        }
    }

    /**
     * Lisans-kritik dosya listesi. Bu listeyi değiştirirsen integrity:rebuild gerekli.
     */
    public function criticalFiles(): array
    {
        return [
            'app/Domain/License/Services/LicenseManager.php',
            'app/Domain/License/Services/JwtVerifier.php',
            'app/Domain/License/Services/KillSwitch.php',
            'app/Domain/License/Services/MachineFingerprint.php',
            'app/Domain/License/Services/OnoxsoftLicenseClient.php',
            'app/Domain/License/Services/PurchaseIntentBuilder.php',
            'app/Domain/License/Services/IntegrityVerifier.php',
            'app/Http/Middleware/EnforceLicense.php',
            'app/Http/Controllers/License/BuyController.php',
            'app/Http/Controllers/License/ActivateController.php',
        ];
    }

    /**
     * SHA256, line-ending normalize'lı (CRLF/LF deploy farkı önemsiz).
     */
    private function computeHash(string $path): string
    {
        $content = @file_get_contents($path);
        if ($content === false) {
            return 'ERROR';
        }
        // Normalize line endings — Windows/Linux deploy uyumu
        $normalized = str_replace(["\r\n", "\r"], "\n", $content);
        return hash('sha256', $normalized);
    }

    /**
     * Cache'i temizle — manifest yenilemesi sonrası çağrılır.
     */
    public function flush(): void
    {
        Cache::forget(self::CACHE_KEY);
    }
}
