<?php

declare(strict_types=1);

namespace App\Service;

use App\Entity\Installation;
use App\Kernel;
use Doctrine\ORM\EntityManagerInterface;

/**
 * Checks for updates from habercms.com.tr API, downloads and applies them.
 */
class UpdateService
{
    public function __construct(
        private readonly EntityManagerInterface $entityManager,
        private readonly string $licenseApiUrl,
        private readonly string $projectDir,
    ) {}

    /**
     * Check for available updates from central API.
     */
    public function checkForUpdate(Installation $installation): ?array
    {
        $body = json_encode([
            'license_key' => $installation->getLicenseKey() ?? '',
            'current_version' => Kernel::VERSION,
        ]);

        $data = $this->curlPost('/api/update/check', $body);
        if ($data === null) {
            return null;
        }

        $installation->setLastUpdateCheck(new \DateTimeImmutable());
        $installation->setAvailableUpdateJson(json_encode($data));
        $this->entityManager->flush();

        return ($data['update_available'] ?? false) ? $data : null;
    }

    /**
     * Download update ZIP to temporary location.
     */
    public function downloadUpdate(Installation $installation, string $version): ?string
    {
        $licenseKey = $installation->getLicenseKey();
        if (!$licenseKey) {
            return null;
        }

        $body = json_encode([
            'license_key' => $licenseKey,
            'version' => $version,
        ]);

        $url = rtrim($this->licenseApiUrl, '/') . '/api/update/download';

        $tmpFile = sys_get_temp_dir() . '/habercms-update-' . $version . '.zip';

        $ch = curl_init($url);
        $fp = fopen($tmpFile, 'w');
        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $body,
            CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
            CURLOPT_FILE => $fp,
            CURLOPT_TIMEOUT => 300,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
        ]);

        curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        fclose($fp);

        if ($httpCode !== 200 || filesize($tmpFile) < 1024) {
            @unlink($tmpFile);
            return null;
        }

        return $tmpFile;
    }

    /**
     * Apply downloaded update.
     * @return array{success: bool, message: string}
     */
    public function applyUpdate(string $zipPath, string $newVersion): array
    {
        // 1. Yedek oluştur
        $backupDir = $this->projectDir . '/var/backup/pre-update-' . date('Ymd-His');
        if (!is_dir($backupDir)) {
            mkdir($backupDir, 0755, true);
        }

        // Kritik dosyaları yedekle
        $backupFiles = ['src', 'config', 'templates', 'vendor', 'public/install.php'];
        foreach ($backupFiles as $path) {
            $full = $this->projectDir . '/' . $path;
            if (is_dir($full)) {
                shell_exec("cp -r " . escapeshellarg($full) . " " . escapeshellarg($backupDir . '/' . $path) . " 2>&1");
            } elseif (is_file($full)) {
                $dir = dirname($backupDir . '/' . $path);
                if (!is_dir($dir)) mkdir($dir, 0755, true);
                copy($full, $backupDir . '/' . $path);
            }
        }

        // .env.local yedekle (üzerine yazılmamalı)
        if (file_exists($this->projectDir . '/.env.local')) {
            copy($this->projectDir . '/.env.local', $backupDir . '/.env.local');
        }

        // 2. ZIP'i aç
        $zip = new \ZipArchive();
        if ($zip->open($zipPath) !== true) {
            return ['success' => false, 'message' => 'ZIP dosyası açılamadı.'];
        }

        $extractDir = sys_get_temp_dir() . '/habercms-extract-' . uniqid();
        $zip->extractTo($extractDir);
        $zip->close();

        // ZIP içinde habercms/ dizini olabilir
        $sourceDir = $extractDir;
        if (is_dir($extractDir . '/habercms')) {
            $sourceDir = $extractDir . '/habercms';
        }

        // 3. Dosyaları kopyala (üzerine yaz)
        // .env, .env.local, .installed, var/, public/uploads/ KORUNACAK
        $protectedPaths = ['.env.local', '.installed', 'var', 'public/uploads'];

        $copyDirs = ['src', 'config', 'templates', 'vendor', 'bin', 'migrations'];
        foreach ($copyDirs as $dir) {
            $from = $sourceDir . '/' . $dir;
            $to = $this->projectDir . '/' . $dir;
            if (is_dir($from)) {
                // Eski dizini sil ve yenisini kopyala
                if (is_dir($to) && !in_array($dir, $protectedPaths)) {
                    shell_exec("rm -rf " . escapeshellarg($to) . " 2>&1");
                }
                shell_exec("cp -r " . escapeshellarg($from) . " " . escapeshellarg($to) . " 2>&1");
            }
        }

        // public/ altındaki dosyaları kopyala (uploads hariç)
        if (is_dir($sourceDir . '/public')) {
            $publicFiles = scandir($sourceDir . '/public') ?: [];
            foreach ($publicFiles as $f) {
                if ($f === '.' || $f === '..' || $f === 'uploads') continue;
                $from = $sourceDir . '/public/' . $f;
                $to = $this->projectDir . '/public/' . $f;
                if (is_file($from)) {
                    copy($from, $to);
                } elseif (is_dir($from)) {
                    shell_exec("cp -r " . escapeshellarg($from) . " " . escapeshellarg($to) . " 2>&1");
                }
            }
        }

        // .env dosyasını güncelle (ama .env.local'ı KORU)
        if (file_exists($sourceDir . '/.env') && file_exists($backupDir . '/.env.local')) {
            copy($sourceDir . '/.env', $this->projectDir . '/.env');
            copy($backupDir . '/.env.local', $this->projectDir . '/.env.local');
        }

        // 4. Schema update
        $phpBin = $this->findPhpBinary();
        shell_exec("cd " . escapeshellarg($this->projectDir) . " && $phpBin bin/console doctrine:schema:update --force --no-interaction --env=prod 2>&1");

        // 5. Cache clear
        shell_exec("cd " . escapeshellarg($this->projectDir) . " && $phpBin bin/console cache:clear --env=prod --no-debug 2>&1");

        // 6. Installation versiyonunu güncelle
        $installation = $this->entityManager->getRepository(Installation::class)->find(1);
        if ($installation) {
            $installation->setCurrentVersion($newVersion);
            $installation->setAvailableUpdateJson(null);
            $this->entityManager->flush();
        }

        // 7. Temizlik
        @unlink($zipPath);
        shell_exec("rm -rf " . escapeshellarg($extractDir) . " 2>&1");

        return ['success' => true, 'message' => "v{$newVersion} başarıyla uygulandı."];
    }

    /**
     * Get cached available update info.
     */
    public function getAvailableUpdate(Installation $installation): ?array
    {
        return $installation->getAvailableUpdate();
    }

    private function curlPost(string $path, string $body): ?array
    {
        $url = rtrim($this->licenseApiUrl, '/') . $path;

        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $body,
            CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
            CURLOPT_TIMEOUT => 10,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
        ]);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($response === false || $httpCode >= 500) {
            return null;
        }

        return json_decode($response, true) ?: null;
    }

    private function findPhpBinary(): string
    {
        $paths = ['/opt/plesk/php/8.3/bin/php', '/opt/plesk/php/8.2/bin/php', '/usr/bin/php8.3', '/usr/bin/php'];
        foreach ($paths as $p) {
            if (file_exists($p)) return $p;
        }
        return 'php';
    }
}
