<?php

namespace App\Services;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\RequestException;
use App\Helpers\Logger;
use App\Models\Site;

class WordPressService
{
    private const BACKPRO_MU_PLUGIN_FILENAME = 'backpro-remote-tools.php';
    private const BACKPRO_REMOTE_SERVICE_FILENAME = 'backpro-remote-service.php';
    private const BACKPRO_REMOTE_SERVICE_VERSION = '1.5.0';
    private const BACKPRO_NEWS_THEME_SLUG = 'backpro-news-mag';
    private const BACKPRO_NEWS_THEME_SOURCE_DIR = 'assets/wp-theme-backpro-news';
    private Client $client;

    public function __construct()
    {
        $this->client = new Client(['timeout' => 30]);
    }

    public function testConnection(array $site): array
    {
        try {
            $options = ['query' => ['per_page' => 1]];
            $auth = $this->buildAuthOption($site);
            if ($auth !== null) {
                $options['auth'] = $auth;
            }

            $response = $this->requestWp($site, 'GET', 'wp/v2/posts', $options);

            if ($response->getStatusCode() === 200) {
                return ['success' => true, 'message' => 'Połączenie OK'];
            }

            return ['success' => false, 'message' => 'Nieoczekiwany kod: ' . $response->getStatusCode()];
        } catch (GuzzleException $e) {
            Logger::error("WP test connection failed for {$site['url']}: " . $e->getMessage(), 'wordpress');
            return ['success' => false, 'message' => 'Błąd: ' . $e->getMessage()];
        }
    }

    public function getCategories(array $site): array|false
    {
        try {
            $options = ['query' => ['per_page' => 100]];
            $auth = $this->buildAuthOption($site);
            if ($auth !== null) {
                $options['auth'] = $auth;
            }

            $response = $this->requestWp($site, 'GET', 'wp/v2/categories', $options);

            $data = json_decode($response->getBody()->getContents(), true);
            if (!is_array($data)) {
                return false;
            }

            if (isset($data['code']) && isset($data['message'])) {
                Logger::error("WP getCategories API error for {$site['url']}: {$data['code']} {$data['message']}", 'wordpress');
                return false;
            }

            return $data;
        } catch (GuzzleException $e) {
            Logger::error("WP getCategories failed for {$site['url']}: " . $e->getMessage(), 'wordpress');
            return false;
        }
    }

    public function createCategory(array $site, string $name, int $parent = 0): ?array
    {
        $auth = $this->requireAuthOption($site, 'createCategory');
        if ($auth === null) {
            return null;
        }

        try {
            $response = $this->requestWp($site, 'POST', 'wp/v2/categories', [
                'auth' => $auth,
                'json' => [
                    'name' => $name,
                    'parent' => $parent,
                ],
            ]);

            return json_decode($response->getBody()->getContents(), true);
        } catch (GuzzleException $e) {
            Logger::error("WP createCategory failed for {$site['url']}: " . $e->getMessage(), 'wordpress');
            return null;
        }
    }

    public function uploadMedia(array $site, string $imageData, string $filename): ?int
    {
        $auth = $this->requireAuthOption($site, 'uploadMedia');
        if ($auth === null) {
            return null;
        }

        // Try REST API first.
        try {
            $response = $this->requestWp($site, 'POST', 'wp/v2/media', [
                'auth' => $auth,
                'headers' => [
                    'Content-Disposition' => "attachment; filename=\"{$filename}\"",
                    'Content-Type' => $this->getMimeType($filename),
                ],
                'body' => $imageData,
            ]);

            $data = json_decode($response->getBody()->getContents(), true);
            if (is_array($data) && isset($data['id'])) {
                return (int) $data['id'];
            }

            Logger::warning("WP REST uploadMedia returned unexpected response for {$site['url']}, trying XML-RPC.", 'wordpress');
        } catch (GuzzleException $e) {
            Logger::warning("WP REST uploadMedia failed for {$site['url']}, trying XML-RPC: " . $e->getMessage(), 'wordpress');
        }

        // Fall back to XML-RPC.
        return $this->uploadMediaXmlRpc($site, $auth, $imageData, $filename);
    }

    public function createPost(
        array $site,
        string $title,
        string $content,
        ?int $categoryId = null,
        ?int $mediaId = null,
        ?string $excerpt = null
    ): ?int {
        $auth = $this->requireAuthOption($site, 'createPost');
        if ($auth === null) {
            return null;
        }

        // Try REST API first.
        try {
            $postData = [
                'title' => $title,
                'content' => $content,
                'status' => 'publish',
            ];

            if ($categoryId) {
                $postData['categories'] = [$categoryId];
            }

            if ($mediaId) {
                $postData['featured_media'] = $mediaId;
            }

            if (is_string($excerpt) && trim($excerpt) !== '') {
                $postData['excerpt'] = trim($excerpt);
            }

            $response = $this->requestWp($site, 'POST', 'wp/v2/posts', [
                'auth' => $auth,
                'json' => $postData,
            ]);

            $data = json_decode($response->getBody()->getContents(), true);
            if (is_array($data) && isset($data['id'])) {
                return (int) $data['id'];
            }

            Logger::warning("WP REST createPost returned unexpected response for {$site['url']}, trying XML-RPC.", 'wordpress');
        } catch (GuzzleException $e) {
            Logger::warning("WP REST createPost failed for {$site['url']}, trying XML-RPC: " . $e->getMessage(), 'wordpress');
        }

        // Fall back to XML-RPC.
        return $this->createPostXmlRpc($site, $auth, $title, $content, $categoryId, $mediaId, $excerpt);
    }

    public function getPublishedPosts(array $site, int $perPage = 100): array|false
    {
        $perPage = max(1, min($perPage, 100));
        $allPosts = [];
        $page = 1;
        $totalPages = 1;
        $auth = $this->buildAuthOption($site);

        try {
            do {
                $query = [
                    'status' => 'publish',
                    'per_page' => $perPage,
                    'page' => $page,
                    '_fields' => 'id,title,content,date,categories,link',
                ];
                $options = ['query' => $query];
                if ($auth !== null) {
                    $options['auth'] = $auth;
                }

                $response = $this->requestWp($site, 'GET', 'wp/v2/posts', $options);
                $batch = json_decode($response->getBody()->getContents(), true);

                // Some hosts return HTML/invalid payload when invalid basic auth is sent.
                if ((!is_array($batch) || (isset($batch['code']) && isset($batch['message']))) && $auth !== null) {
                    $response = $this->requestWp($site, 'GET', 'wp/v2/posts', ['query' => $query]);
                    $batch = json_decode($response->getBody()->getContents(), true);
                }

                if (!is_array($batch)) {
                    throw new \RuntimeException('Invalid JSON response from WordPress posts endpoint.');
                }
                if (isset($batch['code']) && isset($batch['message'])) {
                    throw new \RuntimeException("WordPress API error: {$batch['code']} {$batch['message']}");
                }

                $allPosts = array_merge($allPosts, $batch);

                $totalPages = max(1, (int) $response->getHeaderLine('X-WP-TotalPages'));
                $page++;
            } while ($page <= $totalPages);

            return $allPosts;
        } catch (\Throwable $e) {
            Logger::error("WP getPublishedPosts failed for {$site['url']}: " . $e->getMessage(), 'wordpress');
            return false;
        }
    }

    public function deletePost(array $site, int $wpPostId): bool
    {
        $auth = $this->requireAuthOption($site, 'deletePost');
        if ($auth === null) {
            return false;
        }

        try {
            $this->requestWp($site, 'DELETE', 'wp/v2/posts/' . $wpPostId, [
                'auth' => $auth,
                'query' => ['force' => true],
            ]);
            return true;
        } catch (GuzzleException $e) {
            if ($e instanceof RequestException && $e->hasResponse()) {
                $status = $e->getResponse()->getStatusCode();
                if (in_array($status, [404, 410], true)) {
                    Logger::warning("WP post {$wpPostId} already removed for {$site['url']}", 'wordpress');
                    return true;
                }
            }

            Logger::error("WP deletePost failed for {$site['url']}: " . $e->getMessage(), 'wordpress');
            return false;
        }
    }

    public function getPostFeaturedMedia(array $site, int $wpPostId): ?int
    {
        $auth = $this->requireAuthOption($site, 'getPostFeaturedMedia');
        if ($auth === null) {
            return null;
        }

        try {
            $response = $this->requestWp($site, 'GET', 'wp/v2/posts/' . $wpPostId, [
                'auth' => $auth,
                'query' => ['_fields' => 'featured_media'],
            ]);

            $data = json_decode($response->getBody()->getContents(), true);
            $mediaId = $data['featured_media'] ?? 0;
            return $mediaId > 0 ? $mediaId : null;
        } catch (GuzzleException $e) {
            Logger::error("WP getPostFeaturedMedia failed: " . $e->getMessage(), 'wordpress');
            return null;
        }
    }

    public function updatePostFeaturedMedia(array $site, int $wpPostId, int $mediaId): bool
    {
        $auth = $this->requireAuthOption($site, 'updatePostFeaturedMedia');
        if ($auth === null) {
            return false;
        }

        try {
            $this->requestWp($site, 'POST', 'wp/v2/posts/' . $wpPostId, [
                'auth' => $auth,
                'json' => ['featured_media' => $mediaId],
            ]);
            return true;
        } catch (GuzzleException $e) {
            Logger::error("WP updatePostFeaturedMedia failed: " . $e->getMessage(), 'wordpress');
            return false;
        }
    }

    public function deleteMedia(array $site, int $mediaId): bool
    {
        $auth = $this->requireAuthOption($site, 'deleteMedia');
        if ($auth === null) {
            return false;
        }

        try {
            $this->requestWp($site, 'DELETE', 'wp/v2/media/' . $mediaId, [
                'auth' => $auth,
                'query' => ['force' => true],
            ]);
            return true;
        } catch (GuzzleException $e) {
            Logger::error("WP deleteMedia failed: " . $e->getMessage(), 'wordpress');
            return false;
        }
    }

    public function getPermalinkSettings(array $site): array
    {
        $result = $this->callRemoteService($site, 'get_permalink');
        if (!empty($result['success'])) {
            return [
                'success' => true,
                'permalink_structure' => (string) ($result['permalink_structure'] ?? ''),
                'pretty_enabled' => (bool) ($result['pretty_enabled'] ?? false),
                'source' => 'backpro_remote_service',
                'message' => (string) ($result['message'] ?? 'OK'),
            ];
        }

        $ensure = $this->ensureRemoteService($site);
        if (!empty($ensure['success'])) {
            $refreshedSite = !empty($site['id']) ? (Site::find((int) $site['id']) ?: $site) : $site;
            $retry = $this->callRemoteService($refreshedSite, 'get_permalink');
            if (!empty($retry['success'])) {
                return [
                    'success' => true,
                    'permalink_structure' => (string) ($retry['permalink_structure'] ?? ''),
                    'pretty_enabled' => (bool) ($retry['pretty_enabled'] ?? false),
                    'source' => 'backpro_remote_service',
                    'message' => (string) ($retry['message'] ?? 'OK'),
                ];
            }
        }

        return [
            'success' => false,
            'code' => 'endpoint_missing',
            'message' => (string) ($ensure['message'] ?? $result['message'] ?? 'Brak endpointu BackPRO na WordPress.'),
        ];
    }

    public function enablePrettyPermalinks(array $site): array
    {
        $result = $this->removeIndexPhpFromPermalinks($site);
        $this->ensureHtaccessForPrettyPermalinks($site);

        // Try a dedicated rewrite refresh even when structure did not change.
        $flush = $this->callRemoteService($site, 'flush_rewrite');
        if (empty($flush['success'])) {
            Logger::warning(
                "Remote flush_rewrite failed for {$site['url']}: " . ($flush['message'] ?? 'unknown'),
                'wordpress'
            );
        }

        return $result;
    }

    public function removeIndexPhpFromPermalinks(array $site): array
    {
        $status = $this->getPermalinkSettings($site);
        if (empty($status['success'])) {
            return $status;
        }

        $current = (string) ($status['permalink_structure'] ?? '');
        if ($current === '') {
            return $this->setPermalinkStructure($site, '/%postname%/');
        }

        $updated = preg_replace('#^/?index\.php/?#i', '/', $current);
        $updated = is_string($updated) ? $updated : $current;
        $updated = '/' . ltrim($updated, '/');
        $updated = preg_replace('#/+#', '/', $updated) ?? $updated;

        if ($updated === $current || !str_contains($current, 'index.php')) {
            $this->ensureHtaccessForPrettyPermalinks($site);
            $flush = $this->callRemoteService($site, 'flush_rewrite');
            if (empty($flush['success'])) {
                Logger::warning(
                    "Remote flush_rewrite (no-index branch) failed for {$site['url']}: " . ($flush['message'] ?? 'unknown'),
                    'wordpress'
                );
            }
            return [
                'success' => true,
                'permalink_structure' => $current,
                'pretty_enabled' => true,
                'message' => 'Struktura permalink juz nie zawiera index.php. Odswiezono rewrite i .htaccess.',
            ];
        }

        return $this->setPermalinkStructure($site, $updated);
    }

    public function setPermalinkStructure(array $site, string $structure): array
    {
        $result = $this->callRemoteService($site, 'set_permalink', ['structure' => $structure]);
        if (!empty($result['success'])) {
            $this->ensureHtaccessForPrettyPermalinks($site);
            return [
                'success' => true,
                'message' => (string) ($result['message'] ?? 'Zmieniono strukturę permalink.'),
                'permalink_structure' => (string) ($result['permalink_structure'] ?? $structure),
                'pretty_enabled' => (bool) ($result['pretty_enabled'] ?? true),
            ];
        }

        $ensure = $this->ensureRemoteService($site);
        if (empty($ensure['success'])) {
            return ['success' => false, 'message' => (string) ($ensure['message'] ?? 'Brak endpointu BackPRO na WordPress.')];
        }

        $refreshedSite = !empty($site['id']) ? (Site::find((int) $site['id']) ?: $site) : $site;
        $retry = $this->callRemoteService($refreshedSite, 'set_permalink', ['structure' => $structure]);

        if (!empty($retry['success'])) {
            $this->ensureHtaccessForPrettyPermalinks($refreshedSite);
            return [
                'success' => true,
                'message' => (string) ($retry['message'] ?? 'Zmieniono strukturę permalink.'),
                'permalink_structure' => (string) ($retry['permalink_structure'] ?? $structure),
                'pretty_enabled' => (bool) ($retry['pretty_enabled'] ?? true),
            ];
        }

        return ['success' => false, 'message' => (string) ($retry['message'] ?? 'Blad zmiany permalink.')];
    }

    public function getPostLink(array $site, int $wpPostId): ?string
    {
        if ($wpPostId <= 0) {
            return null;
        }

        $auth = $this->buildAuthOption($site);
        $options = ['query' => ['_fields' => 'link']];
        if ($auth !== null) {
            $options['auth'] = $auth;
        }

        try {
            $response = $this->requestWp($site, 'GET', 'wp/v2/posts/' . $wpPostId, $options);
            $data = json_decode($response->getBody()->getContents(), true);
            $link = trim((string) ($data['link'] ?? ''));
            return $link !== '' ? $link : null;
        } catch (GuzzleException $e) {
            Logger::warning("WP getPostLink failed for {$site['url']}: " . $e->getMessage(), 'wordpress');
            return null;
        }
    }

    public function enableSearchEngineIndexing(array $site): array
    {
        $result = $this->callRemoteService($site, 'set_blog_public', ['blog_public' => '1']);
        if (!empty($result['success'])) {
            return [
                'success' => true,
                'blog_public' => (int) ($result['blog_public'] ?? 1),
                'message' => (string) ($result['message'] ?? 'Wlaczono indeksowanie strony.'),
            ];
        }

        $ensure = $this->ensureRemoteService($site);
        if (empty($ensure['success'])) {
            return [
                'success' => false,
                'message' => (string) ($ensure['message'] ?? $result['message'] ?? 'Brak endpointu BackPRO na WordPress.'),
            ];
        }

        $refreshedSite = !empty($site['id']) ? (Site::find((int) $site['id']) ?: $site) : $site;
        $retry = $this->callRemoteService($refreshedSite, 'set_blog_public', ['blog_public' => '1']);
        if (!empty($retry['success'])) {
            return [
                'success' => true,
                'blog_public' => (int) ($retry['blog_public'] ?? 1),
                'message' => (string) ($retry['message'] ?? 'Wlaczono indeksowanie strony.'),
            ];
        }

        return [
            'success' => false,
            'message' => (string) ($retry['message'] ?? 'Nie udalo sie wlaczyc indeksowania strony.'),
        ];
    }

    public function getCommentSettings(array $site): array
    {
        $result = $this->callRemoteService($site, 'get_comment_settings');
        if (!empty($result['success'])) {
            return $this->formatCommentSettings($result);
        }

        $ensure = $this->ensureRemoteService($site);
        if (empty($ensure['success'])) {
            return [
                'success' => false,
                'comments_enabled' => false,
                'default_comment_status' => '',
                'message' => (string) ($ensure['message'] ?? $result['message'] ?? 'Brak endpointu BackPRO na WordPress.'),
            ];
        }

        $refreshedSite = !empty($site['id']) ? (Site::find((int) $site['id']) ?: $site) : $site;
        $retry = $this->callRemoteService($refreshedSite, 'get_comment_settings');
        if (!empty($retry['success'])) {
            return $this->formatCommentSettings($retry);
        }

        return [
            'success' => false,
            'comments_enabled' => false,
            'default_comment_status' => '',
            'message' => (string) ($retry['message'] ?? 'Nie udalo sie pobrac ustawien komentarzy.'),
        ];
    }

    public function setCommentsEnabled(array $site, bool $enabled): array
    {
        $params = ['comments_enabled' => $enabled ? '1' : '0'];
        $result = $this->callRemoteService($site, 'set_comment_settings', $params);
        if (!empty($result['success'])) {
            return $this->formatCommentSettings($result);
        }

        $ensure = $this->ensureRemoteService($site);
        if (empty($ensure['success'])) {
            return [
                'success' => false,
                'comments_enabled' => !$enabled,
                'default_comment_status' => '',
                'message' => (string) ($ensure['message'] ?? $result['message'] ?? 'Brak endpointu BackPRO na WordPress.'),
            ];
        }

        $refreshedSite = !empty($site['id']) ? (Site::find((int) $site['id']) ?: $site) : $site;
        $retry = $this->callRemoteService($refreshedSite, 'set_comment_settings', $params);
        if (!empty($retry['success'])) {
            return $this->formatCommentSettings($retry);
        }

        return [
            'success' => false,
            'comments_enabled' => !$enabled,
            'default_comment_status' => '',
            'message' => (string) ($retry['message'] ?? 'Nie udalo sie zapisac ustawien komentarzy.'),
        ];
    }

    public function getComments(array $site, string $status = 'all', int $page = 1, int $perPage = 20): array
    {
        $auth = $this->requireAuthOption($site, 'getComments');
        if ($auth === null) {
            return $this->buildCommentsFailure(1, 'Brak danych API WordPress dla tej strony.');
        }

        $page = max(1, $page);
        $perPage = max(1, min($perPage, 100));

        try {
            $response = $this->requestWp($site, 'GET', 'wp/v2/comments', [
                'auth' => $auth,
                'query' => $this->buildCommentsQuery($status, $page, $perPage),
            ]);

            $data = json_decode($response->getBody()->getContents(), true);
            if (!is_array($data)) {
                throw new \RuntimeException('Invalid JSON response from WordPress comments endpoint.');
            }

            if (isset($data['code']) && isset($data['message'])) {
                throw new \RuntimeException("WordPress API error: {$data['code']} {$data['message']}");
            }

            return $this->buildCommentsSuccess($response, $data, $page);
        } catch (RequestException $e) {
            Logger::error("WP getComments failed for {$site['url']}: " . $e->getMessage(), 'wordpress');
            return $this->buildCommentsFailure(
                $page,
                $this->formatWpRequestError($e, 'Nie udalo sie pobrac komentarzy.')
            );
        } catch (\Throwable $e) {
            Logger::error("WP getComments failed for {$site['url']}: " . $e->getMessage(), 'wordpress');
            return $this->buildCommentsFailure($page, 'Nie udalo sie pobrac komentarzy: ' . $e->getMessage());
        }
    }

    public function deleteComment(array $site, int $commentId): array
    {
        if ($commentId <= 0) {
            return ['success' => false, 'message' => 'Nieprawidlowy identyfikator komentarza.'];
        }

        $auth = $this->requireAuthOption($site, 'deleteComment');
        if ($auth === null) {
            return ['success' => false, 'message' => 'Brak danych API WordPress dla tej strony.'];
        }

        try {
            $response = $this->requestWp($site, 'DELETE', 'wp/v2/comments/' . $commentId, [
                'auth' => $auth,
                'query' => ['force' => true],
            ]);

            $data = json_decode($response->getBody()->getContents(), true);
            if (is_array($data) && (isset($data['deleted']) || isset($data['previous']) || isset($data['id']))) {
                return ['success' => true, 'message' => 'Komentarz zostal usuniety.'];
            }

            return ['success' => true, 'message' => 'Komentarz zostal usuniety.'];
        } catch (RequestException $e) {
            Logger::error("WP deleteComment failed for {$site['url']}: " . $e->getMessage(), 'wordpress');
            $statusCode = $e->hasResponse() ? (int) $e->getResponse()->getStatusCode() : 0;
            if ($statusCode === 404) {
                return ['success' => false, 'message' => 'Komentarz nie istnieje albo zostal juz usuniety.'];
            }

            return [
                'success' => false,
                'message' => $this->formatWpRequestError($e, 'Nie udalo sie usunac komentarza.'),
            ];
        } catch (GuzzleException $e) {
            Logger::error("WP deleteComment failed for {$site['url']}: " . $e->getMessage(), 'wordpress');
            return ['success' => false, 'message' => 'Nie udalo sie usunac komentarza: ' . $e->getMessage()];
        }
    }

    public function ensureRemoteService(array $site): array
    {
        $siteData = $this->prepareRemoteServiceMetadata($site);
        $probe = $this->callRemoteService($siteData, 'ping');
        $remoteVersion = (string) ($probe['version'] ?? '');
        if (!empty($probe['success']) && $remoteVersion === self::BACKPRO_REMOTE_SERVICE_VERSION) {
            return ['success' => true, 'message' => 'Remote service juz aktywny.'];
        }

        if (!empty($probe['success'])) {
            Logger::info(
                "Remote service version mismatch for {$siteData['url']}: remote={$remoteVersion}, local=" . self::BACKPRO_REMOTE_SERVICE_VERSION,
                'wordpress'
            );
        }

        return $this->installBackproRemoteService($siteData);
    }

    public function getRemoteServiceStatus(array $site): array
    {
        $siteData = $this->prepareRemoteServiceMetadata($site);
        $probe = $this->callRemoteService($siteData, 'ping');

        return [
            'success' => !empty($probe['success']),
            'local_version' => self::BACKPRO_REMOTE_SERVICE_VERSION,
            'remote_version' => (string) ($probe['version'] ?? 'brak'),
            'service_url' => $this->buildRemoteServiceUrl($siteData),
            'message' => (string) ($probe['message'] ?? 'Brak odpowiedzi pliku serwisowego.'),
        ];
    }

    public function installBackproNewsTheme(array $site): array
    {
        $ftpHost = trim((string) ($site['ftp_host'] ?? ''));
        $ftpUser = trim((string) ($site['ftp_user'] ?? ''));
        $ftpPass = (string) ($site['ftp_pass'] ?? '');
        $ftpPort = (int) ($site['ftp_port'] ?? 21);

        if ($ftpHost === '' || $ftpUser === '' || $ftpPass === '') {
            return ['success' => false, 'message' => 'Brak danych FTP. Uzupelnij je w ustawieniach strony.'];
        }

        $themeSourceDir = dirname(__DIR__, 2) . '/' . self::BACKPRO_NEWS_THEME_SOURCE_DIR;
        if (!is_dir($themeSourceDir)) {
            return ['success' => false, 'message' => 'Brak lokalnych plikow motywu BackPRO News.'];
        }

        $ftp = new FtpService($ftpHost, $ftpUser, $ftpPass, $ftpPort > 0 ? $ftpPort : 21);

        $basePath = trim((string) ($site['ftp_path'] ?? ''), "/ \t\n\r\0\x0B");
        $remoteThemesDir = ($basePath !== '' ? '/' . $basePath : '') . '/wp-content/themes';
        $remoteThemeDir = $remoteThemesDir . '/' . self::BACKPRO_NEWS_THEME_SLUG;

        try {
            $ftp->connect();
            $ftp->ensureDirectory($remoteThemesDir);
            $ftp->ensureDirectory($remoteThemeDir);
            $ftp->deleteDirectoryContents($remoteThemeDir);
            $ftp->uploadDirectory($themeSourceDir, $remoteThemeDir);
            $ftp->disconnect();
        } catch (\Throwable $e) {
            $ftp->disconnect();
            Logger::error("BackPRO News theme upload failed for {$site['url']}: " . $e->getMessage(), 'wordpress');
            return ['success' => false, 'message' => 'Nie udalo sie wgrac motywu: ' . $e->getMessage()];
        }

        $ensure = $this->ensureRemoteService($site);
        if (empty($ensure['success'])) {
            return ['success' => false, 'message' => 'Motyw wgrany, ale nie udalo sie przygotowac pliku serwisowego: ' . ($ensure['message'] ?? '')];
        }

        $siteData = !empty($site['id']) ? (Site::find((int) $site['id']) ?: $site) : $site;
        $activate = $this->callRemoteService($siteData, 'activate_theme', [
            'stylesheet' => self::BACKPRO_NEWS_THEME_SLUG,
        ]);

        if (empty($activate['success'])) {
            return ['success' => false, 'message' => 'Motyw wgrany, ale aktywacja nieudana: ' . ($activate['message'] ?? 'unknown')];
        }

        return [
            'success' => true,
            'message' => 'Zainstalowano i aktywowano motyw BackPRO News. Sekcje kategorii na stronie glownej tworza sie automatycznie.',
        ];
    }

    public function installBackproRemoteService(array $site): array
    {
        $ftpHost = trim((string) ($site['ftp_host'] ?? ''));
        $ftpUser = trim((string) ($site['ftp_user'] ?? ''));
        $ftpPass = (string) ($site['ftp_pass'] ?? '');
        $ftpPort = (int) ($site['ftp_port'] ?? 21);

        if ($ftpHost === '' || $ftpUser === '' || $ftpPass === '') {
            return [
                'success' => false,
                'message' => 'Brak danych FTP. Uzupelnij je w ustawieniach strony.',
            ];
        }

        $siteData = $this->prepareRemoteServiceMetadata($site);
        $serviceFile = (string) $siteData['remote_service_file'];
        $serviceToken = (string) $siteData['remote_service_token'];

        $ftp = new FtpService($ftpHost, $ftpUser, $ftpPass, $ftpPort > 0 ? $ftpPort : 21);
        $tmpFile = tempnam(sys_get_temp_dir(), 'backpro_remote_');

        if ($tmpFile === false) {
            return ['success' => false, 'message' => 'Nie udalo sie przygotowac pliku serwisowego.'];
        }

        $basePath = trim((string) ($siteData['ftp_path'] ?? ''), "/ \t\n\r\0\x0B");
        $remoteDir = ($basePath !== '' ? '/' . $basePath : '');
        $remotePath = rtrim($remoteDir, '/') . '/' . $serviceFile;

        try {
            file_put_contents($tmpFile, $this->getBackproRemoteServiceContent($serviceToken));
            $ftp->connect();
            $ftp->ensureDirectory($remoteDir === '' ? '/' : $remoteDir);
            $ftp->uploadFile($tmpFile, $remotePath);
            $ftp->disconnect();

            if (!empty($siteData['id'])) {
                try {
                    Site::update((int) $siteData['id'], [
                        'remote_service_file' => $serviceFile,
                        'remote_service_token' => $serviceToken,
                        'remote_service_installed_at' => date('Y-m-d H:i:s'),
                    ]);
                } catch (\Throwable $e) {
                    Logger::warning('Could not persist remote service installation metadata: ' . $e->getMessage(), 'wordpress');
                }
            }

            Logger::info("BackPRO remote service uploaded to {$siteData['url']} ({$remotePath})", 'wordpress');
            return ['success' => true, 'message' => 'Zainstalowano plik serwisowy BackPRO na WordPress.'];
        } catch (\Throwable $e) {
            Logger::error("BackPRO remote service upload failed for {$siteData['url']}: " . $e->getMessage(), 'wordpress');
            return ['success' => false, 'message' => 'Nie udalo sie wgrac pliku serwisowego: ' . $e->getMessage()];
        } finally {
            if (is_file($tmpFile)) {
                @unlink($tmpFile);
            }
        }
    }

    // ── XML-RPC fallback methods ──────────────────────────────────────

    private function createPostXmlRpc(
        array $site,
        array $auth,
        string $title,
        string $content,
        ?int $categoryId,
        ?int $mediaId,
        ?string $excerpt
    ): ?int
    {
        $fields = '<member><name>post_title</name><value><string>' . $this->xmlEsc($title) . '</string></value></member>'
            . '<member><name>post_content</name><value><string>' . $this->xmlEsc($content) . '</string></value></member>'
            . '<member><name>post_status</name><value><string>publish</string></value></member>';

        if (is_string($excerpt) && trim($excerpt) !== '') {
            $fields .= '<member><name>mt_excerpt</name><value><string>' . $this->xmlEsc(trim($excerpt)) . '</string></value></member>';
        }

        if ($categoryId) {
            $fields .= '<member><name>terms</name><value><struct>'
                . '<member><name>category</name><value><array><data>'
                . '<value><i4>' . (int) $categoryId . '</i4></value>'
                . '</data></array></value></member>'
                . '</struct></value></member>';
        }

        if ($mediaId) {
            $fields .= '<member><name>post_thumbnail</name><value><i4>' . (int) $mediaId . '</i4></value></member>';
        }

        $xml = $this->xmlRpcEnvelope('wp.newPost', $auth, '<param><value><struct>' . $fields . '</struct></value></param>');

        $result = $this->sendXmlRpc($site['url'], $xml);
        if ($result === null) {
            return null;
        }

        $postId = (int) $result;
        if ($postId <= 0) {
            Logger::error("WP XML-RPC createPost returned invalid id for {$site['url']}.", 'wordpress');
            return null;
        }

        Logger::info("WP XML-RPC createPost success for {$site['url']}: post_id={$postId}", 'wordpress');
        return $postId;
    }

    private function uploadMediaXmlRpc(array $site, array $auth, string $imageData, string $filename): ?int
    {
        $fields = '<member><name>name</name><value><string>' . $this->xmlEsc($filename) . '</string></value></member>'
            . '<member><name>type</name><value><string>' . $this->getMimeType($filename) . '</string></value></member>'
            . '<member><name>bits</name><value><base64>' . base64_encode($imageData) . '</base64></value></member>';

        $xml = $this->xmlRpcEnvelope('wp.uploadFile', $auth, '<param><value><struct>' . $fields . '</struct></value></param>');

        $result = $this->sendXmlRpc($site['url'], $xml, 60);
        if ($result === null) {
            return null;
        }

        // wp.uploadFile returns a struct; parse for 'id' or 'attachment_id'.
        if (is_array($result)) {
            $id = (int) ($result['id'] ?? $result['attachment_id'] ?? 0);
            if ($id > 0) {
                Logger::info("WP XML-RPC uploadFile success for {$site['url']}: media_id={$id}", 'wordpress');
                return $id;
            }
        }

        Logger::error("WP XML-RPC uploadFile returned unexpected response for {$site['url']}.", 'wordpress');
        return null;
    }

    /**
     * Send an XML-RPC request and return the parsed response value, or null on failure.
     */
    private function sendXmlRpc(string $siteUrl, string $xml, int $timeout = 30): mixed
    {
        $url = rtrim($siteUrl, '/') . '/xmlrpc.php';

        try {
            $response = $this->client->request('POST', $url, [
                'body' => $xml,
                'headers' => ['Content-Type' => 'text/xml; charset=UTF-8'],
                'timeout' => $timeout,
            ]);

            $body = $response->getBody()->getContents();
            return $this->parseXmlRpcResponse($body, $siteUrl);
        } catch (GuzzleException $e) {
            Logger::error("WP XML-RPC request failed for {$siteUrl}: " . $e->getMessage(), 'wordpress');
            return null;
        }
    }

    private function xmlRpcEnvelope(string $method, array $auth, string $contentParam): string
    {
        return '<?xml version="1.0" encoding="UTF-8"?>'
            . '<methodCall><methodName>' . $method . '</methodName><params>'
            . '<param><value><i4>1</i4></value></param>'
            . '<param><value><string>' . $this->xmlEsc($auth[0]) . '</string></value></param>'
            . '<param><value><string>' . $this->xmlEsc($auth[1]) . '</string></value></param>'
            . $contentParam
            . '</params></methodCall>';
    }

    private function parseXmlRpcResponse(string $body, string $siteUrl): mixed
    {
        try {
            $prev = libxml_use_internal_errors(true);
            $doc = new \SimpleXMLElement($body);
            libxml_use_internal_errors($prev);
        } catch (\Throwable $e) {
            Logger::error("WP XML-RPC invalid XML from {$siteUrl}: " . $e->getMessage(), 'wordpress');
            return null;
        }

        // Check for fault.
        if (isset($doc->fault)) {
            $members = $doc->fault->value->struct->member ?? [];
            $faultString = 'Unknown';
            foreach ($members as $m) {
                if ((string) $m->name === 'faultString') {
                    $faultString = (string) ($m->value->string ?? $m->value);
                }
            }
            Logger::error("WP XML-RPC fault from {$siteUrl}: {$faultString}", 'wordpress');
            return null;
        }

        // Extract return value.
        $val = $doc->params->param->value ?? null;
        if ($val === null) {
            return null;
        }

        return $this->xmlRpcValue($val);
    }

    private function xmlRpcValue(\SimpleXMLElement $val): mixed
    {
        if (isset($val->string)) {
            return (string) $val->string;
        }
        if (isset($val->int)) {
            return (int) (string) $val->int;
        }
        if (isset($val->i4)) {
            return (int) (string) $val->i4;
        }
        if (isset($val->boolean)) {
            return (bool) (int) (string) $val->boolean;
        }
        if (isset($val->struct)) {
            $result = [];
            foreach ($val->struct->member as $m) {
                $key = (string) $m->name;
                $result[$key] = $this->xmlRpcValue($m->value);
            }
            return $result;
        }
        if (isset($val->array)) {
            $result = [];
            foreach ($val->array->data->value as $v) {
                $result[] = $this->xmlRpcValue($v);
            }
            return $result;
        }

        // Bare <value>text</value> — treat as string.
        return trim((string) $val);
    }

    private function xmlEsc(string $str): string
    {
        return htmlspecialchars($str, ENT_XML1 | ENT_QUOTES, 'UTF-8');
    }

    // ── REST API transport layer ──────────────────────────────────────

    private function requestWp(array $site, string $method, string $route, array $options = [])
    {
        try {
            return $this->requestWpWithOptions($site, $method, $route, $options);
        } catch (RequestException $e) {
            $status = $e->hasResponse() ? (int) $e->getResponse()->getStatusCode() : 0;
            $isRead = strtoupper($method) === 'GET';
            $hasAuth = isset($options['auth']);

            if ($isRead && $hasAuth && in_array($status, [401, 403], true)) {
                $fallbackOptions = $options;
                unset($fallbackOptions['auth']);
                return $this->requestWpWithOptions($site, $method, $route, $fallbackOptions);
            }

            throw $e;
        }
    }

    private function requestWpWithOptions(array $site, string $method, string $route, array $options = [])
    {
        // 1. Try standard /wp-json/ URL.
        try {
            return $this->client->request($method, $this->buildWpJsonUrl($site, $route), $options);
        } catch (RequestException $e) {
            $status = $e->hasResponse() ? (int) $e->getResponse()->getStatusCode() : 0;
            if ($status !== 404) {
                throw $e;
            }
        }

        // 2. Try /index.php/wp-json/ (PATHINFO-based URL).
        //    Works on many hosts without pretty permalinks where /wp-json/ returns 404.
        //    Some hosts ignore PATH_INFO and return the homepage HTML – detect via Content-Type.
        $pathInfoUrl = rtrim($site['url'], '/') . '/index.php/wp-json/' . ltrim($route, '/');
        try {
            $response = $this->client->request($method, $pathInfoUrl, $options);
            $ct = $response->getHeaderLine('Content-Type');
            if (stripos($ct, 'json') !== false) {
                return $response;
            }
            // Non-JSON (HTML page) – host ignores PATH_INFO, try next fallback
        } catch (RequestException $e) {
            $status = $e->hasResponse() ? (int) $e->getResponse()->getStatusCode() : 0;
            if ($status !== 404) {
                throw $e;
            }
        }

        // 3. Fall back to ?rest_route= parameter.
        //    Build URLs with literal slashes (avoid Guzzle encoding / as %2F).
        $extraQuery = $options['query'] ?? [];
        if (!is_array($extraQuery)) {
            $extraQuery = [];
        }
        $fallbackOptions = $options;
        unset($fallbackOptions['query']);

        $rootUrl = $this->buildRestRouteUrl($site['url'], $route, $extraQuery, false);
        $indexUrl = $this->buildRestRouteUrl($site['url'], $route, $extraQuery, true);

        if (strtoupper($method) !== 'GET') {
            // For writes, try /index.php?rest_route= first (no redirects).
            // Some hosts return rest_post_exists on /?rest_route= because the WP
            // main query resolves the homepage ID before the REST handler runs.
            try {
                $noRedirectOpts = $fallbackOptions;
                $noRedirectOpts['allow_redirects'] = false;
                $response = $this->client->request($method, $indexUrl, $noRedirectOpts);
                $statusCode = $response->getStatusCode();
                if ($statusCode < 300 && $this->isRestApiResponse($response)) {
                    return $response;
                }
                // Non-REST response or redirect – fall through to root URL
            } catch (RequestException $e) {
                // /index.php?rest_route= failed – fall through to root URL
            }

            return $this->client->request($method, $rootUrl, $fallbackOptions);
        }

        // For reads, try root URL first, then /index.php
        try {
            return $this->client->request($method, $rootUrl, $fallbackOptions);
        } catch (RequestException $rootError) {
            try {
                return $this->client->request($method, $indexUrl, $fallbackOptions);
            } catch (RequestException $e) {
                throw $rootError;
            }
        }
    }

    /**
     * Check if a response looks like a WordPress REST API response (not a page render).
     * Reads and rewinds the body stream for further consumption by the caller.
     */
    private function isRestApiResponse($response): bool
    {
        $contentType = $response->getHeaderLine('Content-Type');
        if (stripos($contentType, 'json') === false) {
            return false;
        }

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

        if ($response->getBody()->isSeekable()) {
            $response->getBody()->rewind();
        }

        // A valid REST API write response has 'id'/'deleted', an error has 'code'+'message'.
        // The REST API discovery/root response has 'namespaces' – that is NOT a write result.
        if (!is_array($data)) {
            return false;
        }
        if (isset($data['id'])
            || isset($data['deleted'])
            || isset($data['previous'])
            || (isset($data['code']) && isset($data['message']))
        ) {
            return true;
        }

        return false;
    }

    private function buildWpJsonUrl(array $site, string $route): string
    {
        return rtrim($site['url'], '/') . '/wp-json/' . ltrim($route, '/');
    }

    private function buildAuthOption(array $site): ?array
    {
        $user = trim((string) ($site['api_user'] ?? ''));
        $token = trim((string) ($site['api_token'] ?? ''));
        $token = preg_replace('/\s+/', '', $token) ?? $token;

        if ($user === '' || $token === '') {
            return null;
        }

        return [$user, $token];
    }

    private function requireAuthOption(array $site, string $operation): ?array
    {
        $auth = $this->buildAuthOption($site);
        if ($auth !== null) {
            return $auth;
        }

        Logger::error(
            "WP {$operation} skipped for {$site['url']}: missing api_user/api_token in site settings.",
            'wordpress'
        );
        return null;
    }

    private function formatCommentSettings(array $data): array
    {
        $status = (string) ($data['default_comment_status'] ?? '');
        $enabled = array_key_exists('comments_enabled', $data)
            ? (bool) $data['comments_enabled']
            : $status === 'open';

        return [
            'success' => true,
            'default_comment_status' => $status !== '' ? $status : ($enabled ? 'open' : 'closed'),
            'comments_enabled' => $enabled,
            'message' => (string) ($data['message'] ?? 'OK'),
        ];
    }

    private function buildCommentsQuery(string $status, int $page, int $perPage): array
    {
        $allowedStatuses = ['all', 'hold', 'approve', 'spam', 'trash'];

        return [
            'status' => in_array($status, $allowedStatuses, true) ? $status : 'all',
            'page' => $page,
            'per_page' => $perPage,
            'orderby' => 'date',
            'order' => 'desc',
            'context' => 'edit',
        ];
    }

    private function buildCommentsSuccess($response, array $comments, int $page): array
    {
        return [
            'success' => true,
            'comments' => $comments,
            'page' => $page,
            'total_pages' => max(1, (int) $response->getHeaderLine('X-WP-TotalPages')),
            'total' => max(0, (int) $response->getHeaderLine('X-WP-Total')),
            'message' => 'OK',
        ];
    }

    private function buildCommentsFailure(int $page, string $message): array
    {
        return [
            'success' => false,
            'comments' => [],
            'page' => $page,
            'total_pages' => 1,
            'total' => 0,
            'message' => $message,
        ];
    }

    private function formatWpRequestError(RequestException $e, string $fallback): string
    {
        $statusCode = $e->hasResponse() ? (int) $e->getResponse()->getStatusCode() : 0;
        if (in_array($statusCode, [401, 403], true)) {
            return 'Brak uprawnien WordPress API. Sprawdz uzytkownika i haslo aplikacyjne/API token.';
        }

        if ($statusCode === 404) {
            return 'Zasob WordPress nie istnieje albo endpoint REST API jest niedostepny.';
        }

        if ($e->hasResponse()) {
            $body = (string) $e->getResponse()->getBody();
            $data = json_decode($body, true);
            if (is_array($data) && !empty($data['message'])) {
                return (string) $data['message'];
            }
        }

        return $fallback . ' ' . $e->getMessage();
    }

    private function buildRestRouteUrl(string $siteUrl, string $route, array $extraQuery = [], bool $useIndexPhp = false): string
    {
        $base = rtrim($siteUrl, '/');
        $base .= $useIndexPhp ? '/index.php' : '/';

        // Use literal slashes in rest_route to avoid %2F encoding issues on some hosts.
        $restRoute = '/' . ltrim($route, '/');
        $parts = ['rest_route=' . $restRoute];

        foreach ($extraQuery as $key => $value) {
            $parts[] = urlencode((string) $key) . '=' . urlencode((string) $value);
        }

        return $base . '?' . implode('&', $parts);
    }

    private function getMimeType(string $filename): string
    {
        $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
        return match ($ext) {
            'png' => 'image/png',
            'gif' => 'image/gif',
            'webp' => 'image/webp',
            default => 'image/jpeg',
        };
    }

    private function prepareRemoteServiceMetadata(array $site): array
    {
        $data = $site;
        $token = trim((string) ($data['remote_service_token'] ?? ''));
        $file = trim((string) ($data['remote_service_file'] ?? ''));

        $changed = false;
        if ($token === '') {
            $base = (string) ($data['url'] ?? '') . '|' . (string) ($data['api_token'] ?? '') . '|backpro-remote-service';
            $token = substr(hash('sha256', $base), 0, 48);
            if ($token === '') {
                $token = bin2hex(random_bytes(24));
            }
            $data['remote_service_token'] = $token;
            $changed = true;
        }

        if ($file === '') {
            $file = self::BACKPRO_REMOTE_SERVICE_FILENAME;
            $data['remote_service_file'] = $file;
            $changed = true;
        }

        if ($changed && !empty($data['id'])) {
            try {
                Site::update((int) $data['id'], [
                    'remote_service_token' => $token,
                    'remote_service_file' => $file,
                ]);
            } catch (\Throwable $e) {
                Logger::warning('Could not persist remote service metadata: ' . $e->getMessage(), 'wordpress');
            }
        }

        return $data;
    }

    private function buildRemoteServiceUrl(array $site): string
    {
        $siteData = $this->prepareRemoteServiceMetadata($site);
        $file = (string) $siteData['remote_service_file'];
        return rtrim((string) $siteData['url'], '/') . '/' . ltrim($file, '/');
    }

    private function callRemoteService(array $site, string $action, array $params = []): array
    {
        $siteData = $this->prepareRemoteServiceMetadata($site);
        $url = $this->buildRemoteServiceUrl($siteData);
        $token = (string) $siteData['remote_service_token'];

        if ($token === '') {
            return ['success' => false, 'message' => 'Brak tokenu pliku serwisowego BackPRO.'];
        }

        try {
            $response = $this->client->post($url, [
                'timeout' => 20,
                'headers' => ['User-Agent' => 'BackPRO/1.0 Remote-Service'],
                'form_params' => array_merge([
                    'token' => $token,
                    'action' => $action,
                ], $params),
            ]);

            $data = json_decode($response->getBody()->getContents(), true);
            if (!is_array($data)) {
                return ['success' => false, 'message' => 'Nieprawidlowa odpowiedz pliku serwisowego BackPRO.'];
            }

            return $data;
        } catch (\Throwable $e) {
            Logger::warning(
                "Remote service call failed ({$action}) for {$siteData['url']}: " . $e->getMessage(),
                'wordpress'
            );
            return ['success' => false, 'message' => 'Blad komunikacji z plikiem serwisowym: ' . $e->getMessage()];
        }
    }

    private function getBackproRemoteServiceContent(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;
}

if (!defined('ABSPATH')) {
    \$wpLoad = __DIR__ . '/wp-load.php';
    if (!file_exists(\$wpLoad)) {
        http_response_code(500);
        echo json_encode(['success' => false, 'message' => 'wp_load_not_found']);
        exit;
    }
    require_once \$wpLoad;
}

\$action = (string) (\$_POST['action'] ?? '');
if (\$action === 'ping') {
    echo json_encode(['success' => true, 'message' => 'pong', 'version' => '1.5.0']);
    exit;
}

if (\$action === 'get_permalink') {
    \$structure = (string) get_option('permalink_structure', '');
    echo json_encode([
        'success' => true,
        'permalink_structure' => \$structure,
        'pretty_enabled' => trim(\$structure) !== '',
        'message' => 'OK',
    ]);
    exit;
}

if (\$action === 'set_permalink') {
    \$structure = trim((string) (\$_POST['structure'] ?? ''));
    if (\$structure === '') {
        http_response_code(422);
        echo json_encode(['success' => false, 'message' => 'missing_structure']);
        exit;
    }

    update_option('permalink_structure', \$structure);
    // Hard flush rewrites to update .htaccess when possible.
    flush_rewrite_rules(true);
    \$applied = (string) get_option('permalink_structure', '');

    echo json_encode([
        'success' => true,
        'permalink_structure' => \$applied,
        'pretty_enabled' => trim(\$applied) !== '',
        'message' => 'Permalinki zaktualizowane.',
    ]);
    exit;
}

if (\$action === 'flush_rewrite') {
    flush_rewrite_rules(true);
    echo json_encode([
        'success' => true,
        'message' => 'Rewrite rules odswiezone.',
    ]);
    exit;
}

if (\$action === 'activate_theme') {
    \$stylesheet = sanitize_key((string) (\$_POST['stylesheet'] ?? ''));
    if (\$stylesheet === '') {
        http_response_code(422);
        echo json_encode(['success' => false, 'message' => 'missing_stylesheet']);
        exit;
    }

    \$theme = wp_get_theme(\$stylesheet);
    if (!\$theme->exists()) {
        http_response_code(404);
        echo json_encode(['success' => false, 'message' => 'theme_not_found']);
        exit;
    }

    switch_theme(\$stylesheet);
    echo json_encode([
        'success' => true,
        'message' => 'Motyw aktywowany.',
        'active_stylesheet' => get_stylesheet(),
    ]);
    exit;
}

if (\$action === 'set_blog_public') {
    \$blogPublicRaw = (string) (\$_POST['blog_public'] ?? '1');
    \$blogPublic = \$blogPublicRaw === '0' ? 0 : 1;

    update_option('blog_public', \$blogPublic);
    \$applied = (int) get_option('blog_public', 1);

    echo json_encode([
        'success' => true,
        'blog_public' => \$applied,
        'message' => \$applied === 1
            ? 'Indeksowanie przez wyszukiwarki jest wlaczone.'
            : 'Indeksowanie przez wyszukiwarki jest wylaczone.',
    ]);
    exit;
}

if (\$action === 'get_comment_settings') {
    \$status = (string) get_option('default_comment_status', 'open');
    echo json_encode([
        'success' => true,
        'default_comment_status' => \$status,
        'comments_enabled' => \$status === 'open',
        'message' => 'OK',
    ]);
    exit;
}

if (\$action === 'set_comment_settings') {
    \$enabledRaw = (string) (\$_POST['comments_enabled'] ?? '');
    if (\$enabledRaw !== '0' && \$enabledRaw !== '1') {
        http_response_code(422);
        echo json_encode(['success' => false, 'message' => 'missing_comments_enabled']);
        exit;
    }

    \$status = \$enabledRaw === '1' ? 'open' : 'closed';
    update_option('default_comment_status', \$status);
    \$applied = (string) get_option('default_comment_status', 'open');

    echo json_encode([
        'success' => true,
        'default_comment_status' => \$applied,
        'comments_enabled' => \$applied === 'open',
        'message' => \$applied === 'open'
            ? 'Komentowanie nowych wpisow jest wlaczone.'
            : 'Komentowanie nowych wpisow jest wylaczone.',
    ]);
    exit;
}

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

http_response_code(400);
echo json_encode(['success' => false, 'message' => 'invalid_action']);
PHP;
    }

    private function ensureHtaccessForPrettyPermalinks(array $site): void
    {
        $ftpHost = trim((string) ($site['ftp_host'] ?? ''));
        $ftpUser = trim((string) ($site['ftp_user'] ?? ''));
        $ftpPass = (string) ($site['ftp_pass'] ?? '');
        $ftpPort = (int) ($site['ftp_port'] ?? 21);

        if ($ftpHost === '' || $ftpUser === '' || $ftpPass === '') {
            return;
        }

        $ftp = new FtpService($ftpHost, $ftpUser, $ftpPass, $ftpPort > 0 ? $ftpPort : 21);
        $tmpIn = tempnam(sys_get_temp_dir(), 'backpro_hta_in_');
        $tmpOut = tempnam(sys_get_temp_dir(), 'backpro_hta_out_');
        if ($tmpIn === false || $tmpOut === false) {
            return;
        }

        $basePath = trim((string) ($site['ftp_path'] ?? ''), "/ \t\n\r\0\x0B");
        $remoteDir = ($basePath !== '' ? '/' . $basePath : '');
        $remoteHtaccess = rtrim($remoteDir, '/') . '/.htaccess';

        try {
            $ftp->connect();
            $existing = '';
            if ($ftp->downloadFile($remoteHtaccess, $tmpIn) && is_file($tmpIn)) {
                $existing = (string) file_get_contents($tmpIn);
            }

            $updated = $this->injectWordPressHtaccessBlock($existing);
            file_put_contents($tmpOut, $updated);
            $ftp->uploadFile($tmpOut, $remoteHtaccess);

            Logger::info("Ensured .htaccess WordPress rewrite rules for {$site['url']}", 'wordpress');
        } catch (\Throwable $e) {
            Logger::warning("Could not update .htaccess for {$site['url']}: " . $e->getMessage(), 'wordpress');
        } finally {
            $ftp->disconnect();
            @unlink($tmpIn);
            @unlink($tmpOut);
        }
    }

    private function injectWordPressHtaccessBlock(string $content): string
    {
        $block = "# BEGIN WordPress\n"
            . "<IfModule mod_rewrite.c>\n"
            . "RewriteEngine On\n"
            . "RewriteBase /\n"
            . "RewriteRule ^index\\.php$ - [L]\n"
            . "RewriteCond %{REQUEST_FILENAME} !-f\n"
            . "RewriteCond %{REQUEST_FILENAME} !-d\n"
            . "RewriteRule . /index.php [L]\n"
            . "</IfModule>\n"
            . "# END WordPress";

        $trimmed = trim($content);
        if ($trimmed === '') {
            return $block . "\n";
        }

        $pattern = '/# BEGIN WordPress.*?# END WordPress/s';
        if (preg_match($pattern, $content) === 1) {
            return (string) preg_replace($pattern, $block, $content);
        }

        return rtrim($content) . "\n\n" . $block . "\n";
    }

    private function getBackproMuPluginContent(): string
    {
        return <<<'PHP'
<?php
/**
 * Plugin Name: BackPRO Remote Tools (MU)
 * Description: Remote management tools for BackPRO.
 * Version: 1.0.0
 */

if (!defined('ABSPATH')) {
    exit;
}

add_action('rest_api_init', function (): void {
    register_rest_route('backpro/v1', '/permalinks', [
        'methods' => 'GET',
        'callback' => function (): \WP_REST_Response {
            $structure = (string) get_option('permalink_structure', '');
            $pretty = trim($structure) !== '';

            return new \WP_REST_Response([
                'success' => true,
                'source' => 'backpro_mu_plugin',
                'permalink_structure' => $structure,
                'pretty_enabled' => $pretty,
                'message' => $pretty ? 'Przyjazne linki sa wlaczone.' : 'Przyjazne linki sa wylaczone.',
            ], 200);
        },
        'permission_callback' => function (): bool {
            return current_user_can('manage_options');
        },
    ]);

    register_rest_route('backpro/v1', '/permalinks', [
        'methods' => 'POST',
        'callback' => function (\WP_REST_Request $request): \WP_REST_Response {
            $structure = (string) $request->get_param('structure');
            $structure = trim($structure);

            if ($structure === '') {
                return new \WP_REST_Response([
                    'success' => false,
                    'message' => 'Brak struktury permalink.',
                ], 400);
            }

            update_option('permalink_structure', $structure);
            flush_rewrite_rules(false);

            return new \WP_REST_Response([
                'success' => true,
                'permalink_structure' => (string) get_option('permalink_structure', ''),
                'pretty_enabled' => true,
                'message' => 'Zmieniono ustawienie permalink i odswiezono rewrite rules.',
            ], 200);
        },
        'permission_callback' => function (): bool {
            return current_user_can('manage_options');
        },
    ]);
});
PHP;
    }
}
