<?php

declare(strict_types=1);

namespace App\Services;

use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Exception\GuzzleException;
use App\Helpers\Logger;
use App\Models\Article;
use App\Models\Site;
use App\Models\Topic;

class InstallerService
{
    private Client $http;
    private string $tempDir = '';
    private string $progressId = '';

    public function __construct()
    {
        $this->http = new Client(['timeout' => 60, 'verify' => false]);
    }

    private static function progressFilePath(string $id): string
    {
        return sys_get_temp_dir() . '/backpro_progress_' . $id . '.json';
    }

    private function updateProgress(int $percent, string $message, string $status = 'in_progress'): void
    {
        if (empty($this->progressId)) {
            return;
        }
        $data = [
            'percent' => min($percent, 100),
            'message' => $message,
            'status'  => $status,
            'time'    => date('H:i:s'),
        ];
        @file_put_contents(self::progressFilePath($this->progressId), json_encode($data), LOCK_EX);
    }

    public static function getProgress(string $id): ?array
    {
        $file = self::progressFilePath($id);
        if (!file_exists($file)) {
            return null;
        }
        $data = @file_get_contents($file);
        return $data ? json_decode($data, true) : null;
    }

    public static function cleanupProgress(string $id): void
    {
        @unlink(self::progressFilePath($id));
    }

    /**
     * @return array{success: bool, message: string, site_id: int|null}
     */
    public function install(array $config, string $progressId = ''): array
    {
        set_time_limit(600);
        ini_set('memory_limit', '256M');

        $this->progressId = $progressId;

        Logger::info("Starting WordPress installation for {$config['site_url']}", 'installer');
        $this->updateProgress(5, 'Pobieranie WordPress...');

        try {
            // Step 1: Download WordPress
            $zipPath = $this->downloadWordPress($config['language']);
            $this->updateProgress(15, 'Rozpakowywanie archiwum...');

            // Step 2: Extract ZIP
            $wpSourceDir = $this->extractZip($zipPath);
            $this->updateProgress(25, 'Generowanie wp-config.php...');

            // Step 3: Generate wp-config.php
            $this->generateWpConfig($wpSourceDir, $config);
            $this->updateProgress(30, 'Łączenie z serwerem FTP...');

            // Step 4: Upload via FTP
            $this->uploadViaFtp($wpSourceDir, $config);
            $this->updateProgress(85, 'Uruchamianie instalacji WordPress...');

            // Step 5: Trigger WordPress installation
            $this->triggerInstallation($config);
            $this->updateProgress(92, 'Tworzenie Application Password...');

            // Step 6: Create Application Password
            $appPassword = $this->createApplicationPassword($config);
            $this->updateProgress(97, 'Rejestracja strony w BackPRO...');

            // Step 7: Register site in BackPRO (with all credentials)
            $siteId = Site::create([
                'name'                  => $config['site_title'],
                'url'                   => $config['site_url'],
                'api_user'              => $config['admin_user'],
                'api_token'             => $appPassword,
                'publish_interval_hours' => 24,
                'is_active'             => 1,
                'is_multisite'          => 0,
                'ftp_host'              => $config['ftp_host'],
                'ftp_port'              => $config['ftp_port'],
                'ftp_user'              => $config['ftp_user'],
                'ftp_pass'              => $config['ftp_pass'],
                'ftp_path'              => $config['ftp_path'],
                'db_host'               => $config['db_host'],
                'db_name'               => $config['db_name'],
                'db_user'               => $config['db_user'],
                'db_pass'               => $config['db_pass'],
                'db_prefix'             => $config['db_prefix'],
                'wp_admin_user'         => $config['admin_user'],
                'wp_admin_pass'         => $config['admin_pass'],
                'wp_admin_email'        => $config['admin_email'],
            ]);

            $registeredSite = Site::find($siteId);
            if (is_array($registeredSite)) {
                $wpTools = new WordPressService();
                $remoteInstall = $wpTools->ensureRemoteService($registeredSite);
                if (empty($remoteInstall['success'])) {
                    Logger::warning(
                        "Remote service install skipped/failed for site_id={$siteId}: " . ($remoteInstall['message'] ?? 'unknown'),
                        'installer'
                    );
                }
            }

            Logger::info("WordPress installed and site registered (ID: {$siteId})", 'installer');

            $this->updateProgress(100, 'Instalacja zakończona pomyślnie!', 'completed');
            $this->cleanup();

            return [
                'success' => true,
                'message' => "WordPress zainstalowany pomyślnie! Strona \"{$config['site_title']}\" została dodana do BackPRO.",
                'site_id' => $siteId,
            ];
        } catch (\Throwable $e) {
            Logger::error("Installation failed: " . $e->getMessage(), 'installer');
            $this->updateProgress(0, 'Błąd: ' . $e->getMessage(), 'failed');
            $this->cleanup();

            return [
                'success' => false,
                'message' => 'Błąd instalacji: ' . $e->getMessage(),
                'site_id' => null,
            ];
        }
    }

    /**
     * @return array{success: bool, message: string, site_id: int|null}
     */
    public function reinstallSite(array $site, bool $republishPublishedArticles = true, string $progressId = ''): array
    {
        set_time_limit(1200);
        ini_set('memory_limit', '512M');

        $this->progressId = $progressId;

        $config = $this->buildConfigFromSite($site);
        $missing = $this->validateReinstallConfig($config);
        if (!empty($missing)) {
            return [
                'success' => false,
                'message' => 'Brak wymaganych danych do reinstalacji: ' . implode(', ', $missing),
                'site_id' => (int) ($site['id'] ?? 0),
            ];
        }

        $siteId = (int) ($site['id'] ?? 0);
        Logger::warning("Starting WordPress reinstall for site_id={$siteId}, url={$config['site_url']}", 'installer');
        $this->updateProgress(2, 'Przygotowanie reinstalacji WordPress...');

        $articlesToRepublish = $republishPublishedArticles
            ? Article::findPublishedBySiteForRepublish($siteId)
            : [];

        try {
            $this->updateProgress(8, 'Pobieranie WordPress...');
            $zipPath = $this->downloadWordPress($config['language']);

            $this->updateProgress(16, 'Rozpakowywanie archiwum...');
            $wpSourceDir = $this->extractZip($zipPath);

            $this->updateProgress(24, 'Generowanie wp-config.php...');
            $this->generateWpConfig($wpSourceDir, $config);

            $this->updateProgress(30, 'Czyszczenie katalogu FTP...');
            $this->clearRemoteFtpPath($config);

            $this->updateProgress(38, 'Czyszczenie bazy danych...');
            $this->clearDatabase($config);

            $this->updateProgress(44, 'Wgrywanie plikow WordPress...');
            $this->uploadViaFtp($wpSourceDir, $config);

            $this->updateProgress(85, 'Uruchamianie instalacji WordPress...');
            $this->triggerInstallation($config);

            $this->updateProgress(91, 'Tworzenie Application Password...');
            $appPassword = $this->createApplicationPassword($config);

            Site::update($siteId, [
                'name' => $config['site_title'],
                'url' => $config['site_url'],
                'api_user' => $config['admin_user'],
                'api_token' => $appPassword,
                'wp_admin_user' => $config['admin_user'],
                'wp_admin_pass' => $config['admin_pass'],
                'wp_admin_email' => $config['admin_email'],
                'last_published_at' => null,
            ]);

            $runtimeSite = Site::find($siteId);
            if (!$runtimeSite) {
                throw new \RuntimeException('Nie udalo sie odczytac strony po aktualizacji danych API.');
            }

            $this->updateProgress(95, 'Tworzenie kategorii i ponowna publikacja artykulow...');
            $republishStats = $this->republishArticlesAfterReinstall($runtimeSite, $articlesToRepublish);

            $this->updateProgress(100, 'Reinstalacja zakonczona pomyslnie!', 'completed');
            $this->cleanup();

            $message = 'Reinstalacja WordPress zakonczona. ';
            if ($republishPublishedArticles) {
                $message .= "Artykuly odtworzone: {$republishStats['published']}, bledy: {$republishStats['failed']}.";
            } else {
                $message .= 'Pominieto ponowna publikacje artykulow.';
            }

            return [
                'success' => true,
                'message' => $message,
                'site_id' => $siteId,
            ];
        } catch (\Throwable $e) {
            Logger::error("Reinstallation failed for site_id={$siteId}: " . $e->getMessage(), 'installer');
            $this->updateProgress(0, 'Blad: ' . $e->getMessage(), 'failed');
            $this->cleanup();

            return [
                'success' => false,
                'message' => 'Blad reinstalacji: ' . $e->getMessage(),
                'site_id' => $siteId,
            ];
        }
    }

    private function downloadWordPress(string $language): string
    {
        Logger::info("Downloading WordPress ({$language})", 'installer');

        $url = ($language === 'pl_PL')
            ? 'https://pl.wordpress.org/latest-pl_PL.zip'
            : 'https://wordpress.org/latest.zip';

        $this->tempDir = sys_get_temp_dir() . '/backpro_wp_' . uniqid();
        if (!mkdir($this->tempDir, 0755, true)) {
            throw new \RuntimeException('Nie można utworzyć katalogu tymczasowego');
        }

        $zipPath = $this->tempDir . '/wordpress.zip';

        $this->http->get($url, [
            'sink' => $zipPath,
            'timeout' => 120,
            'headers' => [
                'User-Agent' => 'BackPRO/1.0 (WordPress Installer)',
            ],
        ]);

        if (!file_exists($zipPath) || filesize($zipPath) < 1000000) {
            throw new \RuntimeException('Pobieranie WordPress nie powiodło się');
        }

        Logger::info("Downloaded WordPress (" . round(filesize($zipPath) / 1048576, 1) . " MB)", 'installer');
        return $zipPath;
    }

    private function extractZip(string $zipPath): string
    {
        Logger::info("Extracting WordPress ZIP", 'installer');

        $zip = new \ZipArchive();
        $result = $zip->open($zipPath);

        if ($result !== true) {
            throw new \RuntimeException("Nie można otworzyć pliku ZIP (kod błędu: {$result})");
        }

        $extractDir = $this->tempDir . '/extracted';
        $zip->extractTo($extractDir);
        $zip->close();

        $wpDir = $extractDir . '/wordpress';
        if (!is_dir($wpDir)) {
            throw new \RuntimeException('Rozpakowany archiwum nie zawiera katalogu wordpress/');
        }

        @unlink($zipPath);

        Logger::info("Extracted to {$wpDir}", 'installer');
        return $wpDir;
    }

    private function generateWpConfig(string $wpDir, array $config): void
    {
        Logger::info("Generating wp-config.php", 'installer');

        $salts = $this->fetchSalts();

        $dbHost   = addcslashes($config['db_host'], "'\\");
        $dbName   = addcslashes($config['db_name'], "'\\");
        $dbUser   = addcslashes($config['db_user'], "'\\");
        $dbPass   = addcslashes($config['db_pass'], "'\\");
        $dbPrefix = addcslashes($config['db_prefix'], "'\\");

        $wpConfig = <<<PHP
<?php
/**
 * WordPress configuration - generated by BackPRO Installer
 */

// Database settings
define( 'DB_NAME',     '{$dbName}' );
define( 'DB_USER',     '{$dbUser}' );
define( 'DB_PASSWORD', '{$dbPass}' );
define( 'DB_HOST',     '{$dbHost}' );
define( 'DB_CHARSET',  'utf8mb4' );
define( 'DB_COLLATE',  '' );

// Authentication unique keys and salts
{$salts}

// Table prefix
\$table_prefix = '{$dbPrefix}';

// Debug mode
define( 'WP_DEBUG', false );

// Absolute path to the WordPress directory
if ( ! defined( 'ABSPATH' ) ) {
    define( 'ABSPATH', __DIR__ . '/' );
}

// Load WordPress
require_once ABSPATH . 'wp-settings.php';
PHP;

        $configPath = $wpDir . '/wp-config.php';
        if (file_put_contents($configPath, $wpConfig) === false) {
            throw new \RuntimeException('Nie można zapisać wp-config.php');
        }

        Logger::info("wp-config.php generated", 'installer');
    }

    private function fetchSalts(): string
    {
        try {
            $response = $this->http->get('https://api.wordpress.org/secret-key/1.1/salt/', ['timeout' => 10]);
            $salts = $response->getBody()->getContents();

            if (str_contains($salts, 'define(')) {
                return $salts;
            }
        } catch (GuzzleException $e) {
            Logger::warning("Cannot fetch salts from API, generating locally", 'installer');
        }

        $keys = [
            'AUTH_KEY', 'SECURE_AUTH_KEY', 'LOGGED_IN_KEY', 'NONCE_KEY',
            'AUTH_SALT', 'SECURE_AUTH_SALT', 'LOGGED_IN_SALT', 'NONCE_SALT',
        ];
        $lines = [];
        foreach ($keys as $key) {
            $salt = bin2hex(random_bytes(32));
            $lines[] = "define( '{$key}', '{$salt}' );";
        }
        return implode("\n", $lines);
    }

    private function uploadViaFtp(string $wpDir, array $config): void
    {
        Logger::info("Starting FTP upload to {$config['ftp_host']}:{$config['ftp_path']}", 'installer');

        $ftp = new FtpService(
            $config['ftp_host'],
            $config['ftp_user'],
            $config['ftp_pass'],
            $config['ftp_port'],
            $config['ftp_ssl']
        );

        // FTP progress callback: maps file count to 35-85% range
        $ftp->setProgressCallback(function (int $uploaded, int $total) {
            $ftpPercent = ($total > 0) ? ($uploaded / $total) : 0;
            $percent = 35 + (int) ($ftpPercent * 50); // 35% to 85%
            $this->updateProgress($percent, "Wgrywanie plików FTP... ({$uploaded}/{$total})");
        });

        try {
            $ftp->connect();
            $this->updateProgress(35, 'Wgrywanie plików na serwer FTP...');
            $ftp->uploadDirectory($wpDir, $config['ftp_path']);
            Logger::info("FTP upload completed", 'installer');
        } finally {
            $ftp->disconnect();
        }
    }

    private function clearRemoteFtpPath(array $config): void
    {
        Logger::warning("Clearing FTP path {$config['ftp_path']} on {$config['ftp_host']}", 'installer');

        $ftp = new FtpService(
            $config['ftp_host'],
            $config['ftp_user'],
            $config['ftp_pass'],
            $config['ftp_port'],
            $config['ftp_ssl']
        );

        try {
            $ftp->connect();
            $ftp->ensureDirectory($config['ftp_path']);
            $ftp->deleteDirectoryContents($config['ftp_path']);
        } finally {
            $ftp->disconnect();
        }
    }

    private function clearDatabase(array $config): void
    {
        Logger::warning("Clearing database {$config['db_name']} on {$config['db_host']}", 'installer');

        try {
            $this->clearDatabaseViaPdo($config);
            return;
        } catch (\Throwable $e) {
            Logger::warning(
                'Direct DB cleanup failed, trying remote service fallback: ' . $e->getMessage(),
                'installer'
            );
        }

        $this->clearDatabaseViaRemoteService($config);
    }

    private function triggerInstallation(array $config): void
    {
        Logger::info("Triggering WordPress installation at {$config['site_url']}", 'installer');

        $installUrl = $config['site_url'] . '/wp-admin/install.php?step=2';

        try {
            $response = $this->http->post($installUrl, [
                'form_params' => [
                    'weblog_title'    => $config['site_title'],
                    'user_name'       => $config['admin_user'],
                    'admin_password'  => $config['admin_pass'],
                    'admin_password2' => $config['admin_pass'],
                    'admin_email'     => $config['admin_email'],
                    'blog_public'     => 1,
                ],
                'timeout' => 60,
                'allow_redirects' => true,
            ]);

            $body = $response->getBody()->getContents();

            if (
                str_contains($body, 'wp-login.php') ||
                str_contains($body, 'Success') ||
                str_contains($body, 'Udane') ||
                str_contains($body, 'install-success')
            ) {
                Logger::info("WordPress installation triggered successfully", 'installer');
                return;
            }

            Logger::warning("WordPress install response unclear, HTTP " . $response->getStatusCode(), 'installer');
        } catch (GuzzleException $e) {
            throw new \RuntimeException("Instalacja WordPress nie powiodła się: " . $e->getMessage());
        }
    }

    private function createApplicationPassword(array $config): string
    {
        Logger::info("Creating Application Password via WP cookie auth", 'installer');

        sleep(3);

        $jar = new CookieJar();
        $siteUrl = $config['site_url'];

        try {
            // Step 1: Login via wp-login.php to get auth cookies
            Logger::info("Logging in to WordPress admin", 'installer');
            $this->http->post($siteUrl . '/wp-login.php', [
                'form_params' => [
                    'log'         => $config['admin_user'],
                    'pwd'         => $config['admin_pass'],
                    'wp-submit'   => 'Log In',
                    'redirect_to' => $siteUrl . '/wp-admin/',
                    'testcookie'  => '1',
                ],
                'cookies'         => $jar,
                'allow_redirects' => true,
                'timeout'         => 30,
                'headers'         => ['User-Agent' => 'BackPRO/1.0'],
            ]);

            // Step 2: Get REST API nonce via admin-ajax
            Logger::info("Fetching REST API nonce", 'installer');
            $nonceResponse = $this->http->get($siteUrl . '/wp-admin/admin-ajax.php?action=rest-nonce', [
                'cookies' => $jar,
                'timeout' => 15,
                'headers' => ['User-Agent' => 'BackPRO/1.0'],
            ]);
            $nonce = trim($nonceResponse->getBody()->getContents());

            if (empty($nonce) || $nonce === '0' || $nonce === '-1') {
                throw new \RuntimeException('Nie udało się pobrać nonce REST API (logowanie nieudane?)');
            }

            Logger::info("Got REST nonce, creating Application Password", 'installer');

            // Step 3: Create Application Password with cookie auth + nonce
            $apiUrl = $siteUrl . '/?rest_route=/wp/v2/users/me/application-passwords';
            $response = $this->http->post($apiUrl, [
                'cookies' => $jar,
                'headers' => [
                    'X-WP-Nonce'  => $nonce,
                    'User-Agent'  => 'BackPRO/1.0',
                ],
                'json' => [
                    'name' => 'BackPRO ' . date('Y-m-d H:i'),
                ],
                'timeout' => 30,
            ]);

            $data = json_decode($response->getBody()->getContents(), true);

            if (!isset($data['password'])) {
                throw new \RuntimeException('Odpowiedź API nie zawiera hasła aplikacji');
            }

            Logger::info("Application Password created successfully", 'installer');
            return $data['password'];
        } catch (GuzzleException $e) {
            throw new \RuntimeException("Nie można utworzyć Application Password: " . $e->getMessage());
        }
    }

    private function cleanup(): void
    {
        if (!empty($this->tempDir) && is_dir($this->tempDir)) {
            $this->deleteDirectory($this->tempDir);
            Logger::info("Cleaned up temp directory", 'installer');
        }
    }

    private function deleteDirectory(string $dir): void
    {
        if (!is_dir($dir)) {
            return;
        }

        $items = scandir($dir);
        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            $path = $dir . '/' . $item;
            if (is_dir($path)) {
                $this->deleteDirectory($path);
            } else {
                @unlink($path);
            }
        }

        @rmdir($dir);
    }

    private function buildConfigFromSite(array $site): array
    {
        return [
            'ftp_host' => (string) ($site['ftp_host'] ?? ''),
            'ftp_user' => (string) ($site['ftp_user'] ?? ''),
            'ftp_pass' => (string) ($site['ftp_pass'] ?? ''),
            'ftp_path' => rtrim((string) ($site['ftp_path'] ?? ''), '/'),
            'ftp_port' => (int) ($site['ftp_port'] ?? 21),
            'ftp_ssl' => false,
            'db_host' => (string) ($site['db_host'] ?? ''),
            'db_name' => (string) ($site['db_name'] ?? ''),
            'db_user' => (string) ($site['db_user'] ?? ''),
            'db_pass' => (string) ($site['db_pass'] ?? ''),
            'db_prefix' => (string) ($site['db_prefix'] ?? 'wp_'),
            'site_url' => rtrim((string) ($site['url'] ?? ''), '/'),
            'site_title' => (string) ($site['name'] ?? 'WordPress'),
            'admin_user' => (string) ($site['wp_admin_user'] ?? $site['api_user'] ?? ''),
            'admin_pass' => (string) ($site['wp_admin_pass'] ?? ''),
            'admin_email' => (string) ($site['wp_admin_email'] ?? 'admin@example.com'),
            'language' => 'pl_PL',
        ];
    }

    /**
     * @return string[]
     */
    private function validateReinstallConfig(array $config): array
    {
        $required = [
            'ftp_host',
            'ftp_user',
            'ftp_pass',
            'ftp_path',
            'db_host',
            'db_name',
            'db_user',
            'db_pass',
            'site_url',
            'site_title',
            'admin_user',
            'admin_pass',
            'admin_email',
        ];

        $missing = [];
        foreach ($required as $key) {
            if (trim((string) ($config[$key] ?? '')) === '') {
                $missing[] = $key;
            }
        }

        return $missing;
    }

    private function republishArticlesAfterReinstall(array $site, array $articles): array
    {
        $wp = new WordPressService();
        $topicCategoryMap = $this->recreateTopicCategories($site, $wp);

        $published = 0;
        $failed = 0;

        foreach ($articles as $article) {
            $topicId = (int) ($article['topic_id'] ?? 0);
            $categoryId = $topicCategoryMap[$topicId] ?? null;

            $wpPostId = $wp->createPost(
                $site,
                (string) ($article['title'] ?? ''),
                (string) ($article['content'] ?? ''),
                $categoryId,
                null
            );

            if ($wpPostId) {
                $wpPostUrl = $wp->getPostLink($site, (int) $wpPostId);
                Article::update((int) $article['id'], [
                    'wp_post_id' => (int) $wpPostId,
                    'wp_post_url' => $wpPostUrl,
                    'status' => 'published',
                    'error_message' => null,
                ]);
                $published++;
                continue;
            }

            $failed++;
            Logger::error(
                "Republish failed after reinstall. site_id={$site['id']}, article_id={$article['id']}",
                'installer'
            );
        }

        return ['published' => $published, 'failed' => $failed];
    }

    /**
     * @return array<int, int|null> topic_id => wp_category_id
     */
    private function recreateTopicCategories(array $site, WordPressService $wp): array
    {
        $topics = Topic::findBySite((int) $site['id']);
        $map = [];

        foreach ($topics as $topic) {
            $created = $wp->createCategory($site, (string) $topic['name'], 0);
            $wpCategoryId = isset($created['id']) ? (int) $created['id'] : null;

            if ($wpCategoryId !== null && $wpCategoryId > 0) {
                Topic::update((int) $topic['id'], ['wp_category_id' => $wpCategoryId]);
                $map[(int) $topic['id']] = $wpCategoryId;
            } else {
                $map[(int) $topic['id']] = null;
            }
        }

        return $map;
    }

    private function clearDatabaseViaPdo(array $config): void
    {
        $host = (string) $config['db_host'];
        $port = null;
        if (strpos($host, ':') !== false) {
            [$hostOnly, $portPart] = explode(':', $host, 2);
            if (is_numeric($portPart)) {
                $host = $hostOnly;
                $port = (int) $portPart;
            }
        }

        $dsn = "mysql:host={$host};dbname={$config['db_name']};charset=utf8mb4";
        if ($port !== null && $port > 0) {
            $dsn .= ";port={$port}";
        }

        $pdo = new \PDO(
            $dsn,
            (string) $config['db_user'],
            (string) $config['db_pass'],
            [
                \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
                \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
            ]
        );

        $tables = $pdo->query("SHOW FULL TABLES")->fetchAll(\PDO::FETCH_NUM);
        $pdo->exec('SET FOREIGN_KEY_CHECKS=0');

        foreach ($tables as $row) {
            $tableName = (string) ($row[0] ?? '');
            $tableType = strtoupper((string) ($row[1] ?? 'BASE TABLE'));
            if ($tableName === '') {
                continue;
            }

            if ($tableType === 'VIEW') {
                $pdo->exec("DROP VIEW IF EXISTS `{$tableName}`");
            } else {
                $pdo->exec("DROP TABLE IF EXISTS `{$tableName}`");
            }
        }

        $pdo->exec('SET FOREIGN_KEY_CHECKS=1');
    }

    private function clearDatabaseViaRemoteService(array $config): void
    {
        $token = bin2hex(random_bytes(24));
        $scriptName = 'backpro-service-' . bin2hex(random_bytes(6)) . '.php';
        $serviceContent = $this->getRemoteDbServiceScriptContent($token);
        $tmpFile = tempnam(sys_get_temp_dir(), 'backpro_service_');

        if ($tmpFile === false) {
            throw new \RuntimeException('Cannot create temporary file for remote DB service.');
        }

        $ftp = new FtpService(
            $config['ftp_host'],
            $config['ftp_user'],
            $config['ftp_pass'],
            $config['ftp_port'],
            $config['ftp_ssl']
        );

        $remoteDir = '/' . trim((string) $config['ftp_path'], '/');
        $remoteScriptPath = rtrim($remoteDir, '/') . '/' . $scriptName;

        try {
            file_put_contents($tmpFile, $serviceContent);
            $ftp->connect();
            $ftp->ensureDirectory($remoteDir);
            $ftp->uploadFile($tmpFile, $remoteScriptPath);
            $ftp->disconnect();

            $endpoint = rtrim((string) $config['site_url'], '/') . '/' . $scriptName;

            $response = $this->http->post($endpoint, [
                'form_params' => [
                    'token' => $token,
                    'action' => 'db_clear',
                    'db_host' => (string) $config['db_host'],
                    'db_name' => (string) $config['db_name'],
                    'db_user' => (string) $config['db_user'],
                    'db_pass' => (string) $config['db_pass'],
                ],
                'timeout' => 120,
                'headers' => [
                    'User-Agent' => 'BackPRO/1.0 Remote-Service',
                ],
            ]);

            $data = json_decode($response->getBody()->getContents(), true);
            if (!is_array($data) || empty($data['success'])) {
                $message = is_array($data) ? (string) ($data['message'] ?? 'unknown error') : 'invalid response';
                throw new \RuntimeException('Remote DB cleanup failed: ' . $message);
            }

            try {
                $this->http->post($endpoint, [
                    'form_params' => [
                        'token' => $token,
                        'action' => 'cleanup',
                    ],
                    'timeout' => 20,
                ]);
            } catch (\Throwable $e) {
                Logger::warning('Remote service cleanup request failed: ' . $e->getMessage(), 'installer');
            }

            Logger::info('Database cleaned through remote service endpoint.', 'installer');
        } finally {
            if (is_file($tmpFile)) {
                @unlink($tmpFile);
            }
        }
    }

    private function getRemoteDbServiceScriptContent(string $token): string
    {
        $safeToken = addslashes($token);

        return <<<PHP
<?php
header('Content-Type: application/json; charset=utf-8');

if (\$_SERVER['REQUEST_METHOD'] !== 'POST') {
    http_response_code(405);
    echo json_encode(['success' => false, 'message' => 'method_not_allowed']);
    exit;
}

\$expectedToken = '{$safeToken}';
\$providedToken = (string) (\$_POST['token'] ?? '');

if (!hash_equals(\$expectedToken, \$providedToken)) {
    http_response_code(403);
    echo json_encode(['success' => false, 'message' => 'forbidden']);
    exit;
}

\$action = (string) (\$_POST['action'] ?? '');
if (\$action === 'cleanup') {
    @unlink(__FILE__);
    echo json_encode(['success' => true, 'message' => 'service_deleted']);
    exit;
}

if (\$action !== 'db_clear') {
    http_response_code(400);
    echo json_encode(['success' => false, 'message' => 'invalid_action']);
    exit;
}

\$dbHost = (string) (\$_POST['db_host'] ?? '');
\$dbName = (string) (\$_POST['db_name'] ?? '');
\$dbUser = (string) (\$_POST['db_user'] ?? '');
\$dbPass = (string) (\$_POST['db_pass'] ?? '');

if (\$dbHost === '' || \$dbName === '' || \$dbUser === '') {
    http_response_code(422);
    echo json_encode(['success' => false, 'message' => 'missing_db_params']);
    exit;
}

try {
    \$host = \$dbHost;
    \$port = null;
    if (strpos(\$host, ':') !== false) {
        list(\$hostOnly, \$portPart) = explode(':', \$host, 2);
        if (is_numeric(\$portPart)) {
            \$host = \$hostOnly;
            \$port = (int) \$portPart;
        }
    }

    \$dsn = "mysql:host={\$host};dbname={\$dbName};charset=utf8mb4";
    if (\$port !== null && \$port > 0) {
        \$dsn .= ";port={\$port}";
    }

    \$pdo = new PDO(\$dsn, \$dbUser, \$dbPass, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ]);

    \$tables = \$pdo->query("SHOW FULL TABLES")->fetchAll(PDO::FETCH_NUM);
    \$pdo->exec('SET FOREIGN_KEY_CHECKS=0');

    foreach (\$tables as \$row) {
        \$tableName = (string) (\$row[0] ?? '');
        \$tableType = strtoupper((string) (\$row[1] ?? 'BASE TABLE'));
        if (\$tableName === '') {
            continue;
        }

        if (\$tableType === 'VIEW') {
            \$pdo->exec("DROP VIEW IF EXISTS `{\$tableName}`");
        } else {
            \$pdo->exec("DROP TABLE IF EXISTS `{\$tableName}`");
        }
    }

    \$pdo->exec('SET FOREIGN_KEY_CHECKS=1');
    echo json_encode(['success' => true, 'message' => 'db_cleared']);
} catch (Throwable \$e) {
    http_response_code(500);
    echo json_encode(['success' => false, 'message' => \$e->getMessage()]);
}
PHP;
    }
}
