<?php
declare(strict_types=1);
namespace App\Domain\License\Services;

/**
 * Sunucu fingerprint — IP değişebilir, hostname değişebilir, ama machineId stabil kalır.
 *
 * machineId() kaynak öncelikleri:
 *  1. Linux:   /etc/machine-id (systemd standart, fresh install hariç sabit)
 *  2. macOS:   IOPlatformUUID (donanım bazlı, format-değişmez)
 *  3. Windows: WMIC csproduct UUID (donanım UUID, kalıcı)
 *  4. Fallback: MAC adresleri (eth0/en0) + storage path hash — son çare
 *
 * hostname() salt bilgi amaçlı — lisans doğrulama için kullanılmaz.
 * License Server'da bound_ip ile machine_id eşleşmesi kontrol edilir,
 * hostname değişikliği lisansı geçersiz kılmaz.
 */
class MachineFingerprint {
    private ?string $cachedMachineId = null;
    private ?string $cachedHostname  = null;
    private ?string $cachedPublicIp  = null;

    public function machineId(): string {
        if ($this->cachedMachineId !== null) return $this->cachedMachineId;

        // 1. Linux: /etc/machine-id (en stabil)
        if (PHP_OS_FAMILY === 'Linux' && is_readable('/etc/machine-id')) {
            $id = trim(@file_get_contents('/etc/machine-id') ?: '');
            if ($id && strlen($id) === 32) {
                return $this->cachedMachineId = $id;
            }
        }

        // 2. macOS: ioreg
        if (PHP_OS_FAMILY === 'Darwin') {
            $output = @shell_exec('ioreg -d2 -c IOPlatformExpertDevice 2>/dev/null | awk -F\\" \'/IOPlatformUUID/{print $(NF-1)}\'');
            if ($output) {
                $id = strtolower(trim($output));
                if ($id) return $this->cachedMachineId = $id;
            }
        }

        // 3. Windows: WMIC csproduct UUID
        if (PHP_OS_FAMILY === 'Windows') {
            $output = @shell_exec('wmic csproduct get UUID /value 2>nul');
            if ($output && preg_match('/UUID=([A-F0-9-]+)/i', $output, $m)) {
                return $this->cachedMachineId = strtolower($m[1]);
            }
        }

        // 4. Fallback: MAC + path hash
        $mac = $this->primaryMacAddress();
        $pathSeed = function_exists('storage_path') && function_exists('app') && app()->bound('path.storage')
            ? storage_path()
            : sys_get_temp_dir();

        // hostname'i fallback'e EKLEMEDIK — değişebilir
        $hash = hash('sha256', $mac . '|' . PHP_OS_FAMILY . '|' . $pathSeed);
        return $this->cachedMachineId = substr($hash, 0, 32);
    }

    /**
     * Sunucunun gerçek public IPv4 adresi.
     *
     * Kaynak öncelikleri:
     *  1. ONOX_PUBLIC_IP env var (operatörün manuel set ettiği — en güvenilir)
     *  2. config('onoxsoft.server_ip') override
     *  3. `ip route get 8.8.8.8` (default route üzerinden çıkışta kullanılan src IP)
     *  4. `hostname -I` (Linux, ilk non-loopback IP)
     *  5. Outbound HTTP discovery: ifconfig.me / api.ipify.org / icanhazip.com
     *  6. SERVER_ADDR / $request->ip() fallback (en kötü ihtimal)
     *
     * Sonuç 6 saat Laravel Cache::remember edilir (Redis varsa) — her sayfa load'da
     * outbound call yapmaz. Public IP'ler nadiren değişir.
     */
    public function publicIp(): string {
        if ($this->cachedPublicIp !== null) return $this->cachedPublicIp;

        // 1. Manuel env override (en güvenli — operatör install.sh'da set eder)
        $envIp = getenv('ONOX_PUBLIC_IP') ?: env('ONOX_PUBLIC_IP');
        if ($envIp && $this->isValidPublicIp($envIp)) {
            return $this->cachedPublicIp = $envIp;
        }

        // 2. Config override
        if (function_exists('config')) {
            $configIp = config('onoxsoft.server_ip');
            if ($configIp && $this->isValidPublicIp($configIp)) {
                return $this->cachedPublicIp = $configIp;
            }
        }

        // 3-6. Cache (6 saat) — discovery işlemleri masraflı
        if (function_exists('cache')) {
            $cached = cache()->remember('onox.public_ip', 21600, fn () => $this->discoverPublicIp());
            if ($cached) return $this->cachedPublicIp = $cached;
        }

        return $this->cachedPublicIp = $this->discoverPublicIp() ?: '0.0.0.0';
    }

    /**
     * Multi-source public IP discovery (cache miss durumunda).
     */
    private function discoverPublicIp(): ?string {
        // 3. ip route — Linux'ta default route üzerinden src IP
        if (PHP_OS_FAMILY === 'Linux') {
            $output = @shell_exec("ip -4 route get 8.8.8.8 2>/dev/null | awk '/src/ {for(i=1;i<=NF;i++) if(\$i==\"src\") print \$(i+1)}'");
            $ip = trim($output ?? '');
            if ($ip && $this->isValidPublicIp($ip)) return $ip;
        }

        // 4. hostname -I — ilk non-loopback IP
        if (PHP_OS_FAMILY === 'Linux') {
            $output = @shell_exec('hostname -I 2>/dev/null');
            foreach (preg_split('/\s+/', trim($output ?? '')) as $candidate) {
                if ($this->isValidPublicIp($candidate)) return $candidate;
            }
        }

        // 5. Outbound HTTP discovery (3 source — round-robin)
        // Production'da her zaman çalışır (sunucu internete bağlı).
        // Dev'de Windows XAMPP üzerinden de çalışır (PHP outbound izinleri açıksa).
        $sources = [
            'https://api.ipify.org',
            'https://ifconfig.me/ip',
            'https://icanhazip.com',
        ];
        foreach ($sources as $url) {
            try {
                $ctx = stream_context_create([
                    'http' => ['timeout' => 3, 'user_agent' => 'Onoxsoft-Panel/1.0'],
                    'https' => ['timeout' => 3, 'user_agent' => 'Onoxsoft-Panel/1.0'],
                ]);
                $body = @file_get_contents($url, false, $ctx);
                if ($body !== false) {
                    $ip = trim($body);
                    if ($this->isValidPublicIp($ip)) return $ip;
                }
            } catch (\Throwable) {
                continue;
            }
        }

        // 6. En son çare — Windows / firewall'lu durumlarda
        if (!empty($_SERVER['SERVER_ADDR']) && $this->isValidPublicIp($_SERVER['SERVER_ADDR'])) {
            return $_SERVER['SERVER_ADDR'];
        }

        return null;
    }

    /**
     * Public (routable) IPv4 check — 127.x, 10.x, 172.16-31.x, 192.168.x reddet.
     */
    private function isValidPublicIp(string $ip): bool {
        return filter_var(
            $ip,
            FILTER_VALIDATE_IP,
            FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
        ) !== false;
    }

    /**
     * Sadece bilgi amaçlı — lisans doğrulamada KULLANILMAZ.
     * Sunucu hostname'i hosting şirketi tarafından değiştirilebilir.
     */
    public function hostname(): string {
        if ($this->cachedHostname !== null) return $this->cachedHostname;
        return $this->cachedHostname = gethostname() ?: php_uname('n') ?: 'unknown-host';
    }

    /**
     * machineId yalnız — fingerprint() çağrısı yapan eski caller'lar için
     * (artık hostname dahil değil).
     */
    public function fingerprint(): string {
        return $this->machineId();
    }

    /**
     * Birincil network interface'in MAC adresini bul.
     * Linux: sysfs net interface address dosyalari. Windows/macOS: getmac / ifconfig.
     */
    private function primaryMacAddress(): string {
        // Linux
        if (PHP_OS_FAMILY === 'Linux' && is_dir('/sys/class/net')) {
            foreach (['eth0', 'ens3', 'ens5', 'enp0s3', 'enp1s0', 'wlan0'] as $iface) {
                $path = "/sys/class/net/$iface/address";
                if (is_readable($path)) {
                    $mac = trim(@file_get_contents($path) ?: '');
                    if ($mac && $mac !== '00:00:00:00:00:00') return $mac;
                }
            }
            // Fallback: ilk non-loopback
            foreach (glob('/sys/class/net/*/address') ?: [] as $file) {
                if (str_contains($file, '/lo/')) continue;
                $mac = trim(@file_get_contents($file) ?: '');
                if ($mac && $mac !== '00:00:00:00:00:00') return $mac;
            }
        }

        // Windows
        if (PHP_OS_FAMILY === 'Windows') {
            $output = @shell_exec('getmac /fo csv /nh 2>nul');
            if ($output && preg_match('/"([0-9A-F-]{17})"/i', $output, $m)) {
                return strtolower(str_replace('-', ':', $m[1]));
            }
        }

        // macOS
        if (PHP_OS_FAMILY === 'Darwin') {
            $output = @shell_exec("ifconfig en0 2>/dev/null | awk '/ether/{print \$2}'");
            if ($output) {
                $mac = trim($output);
                if ($mac) return $mac;
            }
        }

        return 'unknown-mac';
    }
}
