<?php

namespace WPIS;

use DateTime;

use WPIS\ImmosyncClient;

require_once 'PostTypesManager.php';
require_once __DIR__ . '/../utilities/CacheManager.php';
if (defined('ABSPATH')) {
    require_once ABSPATH . 'wp-admin/includes/image.php';
}

class SyncManager
{

    private const SYNC_UPDATE = 'UPDATE';
    private const SYNC_CREATE = 'CREATE';
    private const SYNC_SKIP = 'SKIP';

    private $postTypeProjectName;
    private $postTypeAgentName;
    private $debug;

    private $enableImportSystem = false;
    private $enableImportCustomPostType = false;
    private $enableProjectManagement = false;
    private $enableProjectManagementCustomPostType = false;
    private $enableAgentManagement = false;
    private $enableAgentManagementCustomPostType = false;

    private $enableTaxonomies = false;
    private $autoRemoveUpdatedEstates = false;
    private $enablePartialRemove = false;
    private $expiredLockTime = 600;
    private $cacheDirectory;
    private $metaStack = [];
    public  $cache;
    public  $wpdb;
    public  $cacheFile;
    public  $lockFile;
    public  $postTypeEstateName;

    static public function init()
    {

        error_reporting(E_ALL);
        ini_set('display_errors', 1);

        $class = new SyncManager();

        $class->cacheDirectory = \wp_get_upload_dir()['basedir'] . '/wp-immo-sync';

        if (! \is_dir($class->cacheDirectory)) {
            \mkdir($class->cacheDirectory);
        }

        $class->debug = SettingsManager::get('debug', false);

        $class->cache = new CacheManager($class->cacheDirectory . '/' . 'cache');

        $class->wpdb = DatabaseManager::db();

        $class->cacheFile = $class->cacheDirectory . '/wpis-cache.php';
        $class->lockFile = $class->cacheDirectory . '/wpis.lock';

        $class->checkSettings();

        if ($class->enableImportSystem) {
            $class->postTypeEstateName =
                $class->enableImportCustomPostType ?
                SettingsManager::get('customPostType') :
                EstatePostType::$postTypeName;
        }

        if ($class->enableProjectManagement) {
            $class->postTypeProjectName =
                $class->enableProjectManagementCustomPostType ?
                SettingsManager::get('projectCustomPostType') :
                ProjectPostType::$postTypeName;
        }

        if ($class->enableAgentManagement) {
            $class->postTypeAgentName =
                $class->enableAgentManagementCustomPostType ?
                SettingsManager::get('agentCustomPostType') :
                AgentPostType::$postTypeName;
        }

        // Remove revisions support to avoid third party plugins to create revisions
        \remove_post_type_support($class->postTypeEstateName, 'revisions');
        \remove_post_type_support($class->postTypeProjectName, 'revisions');
        \remove_post_type_support($class->postTypeAgentName, 'revisions');

        return $class;
    }

    private function checkSettings()
    {
        $this->enableImportSystem                    = SettingsManager::get('enableImportSystem');
        $this->enableImportCustomPostType            = SettingsManager::get('enableImportCustomPostType');
        $this->autoRemoveUpdatedEstates              = SettingsManager::get('autoRemoveUpdatedEstates');
        $this->enableTaxonomies                      = SettingsManager::get('enableTaxonomies');
        $this->enableProjectManagement               = SettingsManager::get('enableProjectManagement');
        $this->enableProjectManagementCustomPostType = SettingsManager::get('enableProjectManagementCustomPostType');
        $this->enableAgentManagement                 = SettingsManager::get('enableAgentManagement');
        $this->enableAgentManagementCustomPostType   = SettingsManager::get('enableAgentManagementCustomPostType');
        $this->enablePartialRemove                   = SettingsManager::get('enablePartialRemove');
    }

    public function prepareEstateSync(): array
    {

        $start = microtime(true);
        $request = ImmosyncClient::init()->request('GET', 'estates', ['limit' => 1000]);


        if (isset($request->data->total) && json_decode($request->data)->total === 0) {
            return [
                'success' => false,
                'message' => 'There is no estate to import.',
                'code' => 1000
            ];
        }

        if ($request->code !== 200 || !json_decode($request->data)->data) {

            Logger::log('wpis-sync', [
                '[SYNC] ERROR | Buffer failure',
                '[SYNC] > CODE : ' . $request->code,
                '[SYNC] > RESPONSE : ' . $request->data
            ]);

            return [
                'success' => false,
                'message' => 'An error occured while retrieving estates.' . json_encode($request),
                'code' => 1001,
                'data' => $request->data
            ];
        }

        $response = json_decode($request->data);
        $remoteEstates = $response->data;

        // Improved estate query: formatted for readability, uses table aliases, clearer structure, and avoids redundant subqueries.
        $posts_table = $this->wpdb->posts;
        $postmeta_table = $this->wpdb->postmeta;
        $meta_prefix = PostTypesManager::$postTypeMetaPrefix;

        $query = $this->wpdb->prepare(
            "
            SELECT
                p.ID as postId,
                pm_estateId.meta_value as estateId,
                pm_locked.meta_value as locked,
                pm_undeletable.meta_value as undeletable,
                CASE
                    WHEN pm_pictures.meta_value IS NULL OR pm_pictures.meta_value = ''
                        THEN 0
                    ELSE 1
                END as picturesHashChanged
            FROM {$posts_table} p
            INNER JOIN {$postmeta_table} pm_estateId
                ON pm_estateId.post_id = p.ID AND pm_estateId.meta_key = %s
            LEFT JOIN {$postmeta_table} pm_locked
                ON pm_locked.post_id = p.ID AND pm_locked.meta_key = 'wpis_locked'
            LEFT JOIN {$postmeta_table} pm_undeletable
                ON pm_undeletable.post_id = p.ID AND pm_undeletable.meta_key = 'wpis_undeletable'
            LEFT JOIN {$postmeta_table} pm_pictures
                ON pm_pictures.post_id = p.ID AND pm_pictures.meta_key = 'wpis_picturesHashChanged'
            LEFT JOIN {$postmeta_table} pm_softDeleted
                ON pm_softDeleted.post_id = p.ID AND pm_softDeleted.meta_key = %s
            WHERE p.post_type IN (%s, %s)
                AND (pm_softDeleted.meta_value IS NULL OR pm_softDeleted.meta_value = '' OR pm_softDeleted.meta_value = '0')
            ",
            "{$meta_prefix}_id",
            "{$meta_prefix}_soft_deleted",
            $this->postTypeEstateName,
            $this->postTypeProjectName
        );


        $existingEstates = $this->wpdb->get_results($query);
        $existingEstatesMap = [];

        // Map estate imported in associative array
        foreach ($existingEstates as $e) {
            $existingEstatesMap[$e->estateId] = $e;
        }

        $totalImported = count($existingEstatesMap);
        $totalToImport = $response->total;
        $remoteEstatesMap = [];

        foreach ($remoteEstates as $e) {
            $remoteEstatesMap[$e->id] = [
                'id'               => $e->id,
                'imported'         => isset($existingEstatesMap[$e->id]),
                'locked'           => isset($existingEstatesMap[$e->id]) && $existingEstatesMap[$e->id]->locked,
                'pictures_updated' => isset($existingEstatesMap[$e->id]) && $existingEstatesMap[$e->id]->picturesHashChanged,
                'data'             => $e,
            ];
        }

        // Create cache
        $this->cache->set('data', ['estates'  => $remoteEstatesMap]);

        $estateToRemove = [];

        foreach ($existingEstatesMap as $estateId => $estate) {
            if ($estate->locked || $estate->undeletable) {
                continue;
            }

            $remoteEstate = $remoteEstatesMap[$estateId] ?? null;
            if ($remoteEstate && $this->autoRemoveUpdatedEstates && $remoteEstate['pictures_updated']) {
                $estateToRemove[$estateId] = $estate;
            }

            if (!$remoteEstate) {
                if (get_post_meta($estate->postId, PostTypesManager::$postTypeMetaPrefix . '_soft_deleted', true)) {
                    continue;
                }
                $estateToRemove[$estateId] = $estate;
            }
        }

        foreach ($estateToRemove as $key => $estate) {
            $type = \get_post_meta($estate->postId, PostTypesManager::$postTypeMetaPrefix . '_' . 'category_label', true);
            $category = \get_post_meta($estate->postId, PostTypesManager::$postTypeMetaPrefix . '_' . 'subcategory_label', true);
            $purpose = \get_post_meta($estate->postId, PostTypesManager::$postTypeMetaPrefix . '_' . 'purpose_label', true);
            $typeCategory = Tools::ucfirst($type) . ' / ' . Tools::ucfirst($category);
            $price = round(intval(\get_post_meta($estate->postId, PostTypesManager::$postTypeMetaPrefix . '_' . 'finance_price', true)));
            $price = number_format_i18n($price) . ' €';
            $price = str_replace(',', ' ', $price);

            $estateToRemove[$key]->postTitle = \get_post_meta($estate->postId, PostTypesManager::$postTypeMetaPrefix . '_' . 'name', true);
            $estateToRemove[$key]->reference = \get_post_meta($estate->postId, PostTypesManager::$postTypeMetaPrefix . '_' . 'reference', true);
            $estateToRemove[$key]->price = $price;
            $estateToRemove[$key]->category = $typeCategory;
            $estateToRemove[$key]->purpose = Tools::ucfirst($purpose);
            $estateToRemove[$key]->postDateImported = \get_the_time('d-m-Y', $estate->postId);
        }

        Logger::log('wpis-sync', '[SYNC] BUFFER | Imported: ' . $totalImported . ' | Total: ' . $totalToImport . ' | To Remove: ' . count($estateToRemove) . ' | Duration: ' . round(microtime(true) - $start, 3));

        // Get last import date time;
        $lastUpdateAt = \get_option('wpis_last_update', false);

        return [
            'success'         => true,
            'totalImported'   => $totalImported,
            'estateTotal'     => $totalToImport,
            'estatesToRemove' => $estateToRemove,
            'lastUpdateAt'    => $lastUpdateAt,
            'messages'        => $response->messages ?? []
        ];
    }

    public function next($settings)
    {

        if ($this->isSyncLocked()) {
            return [
                'success' => false,
                'message' => 'An import is currently running',
            ];
        }

        $this->lockSync();

        if ($this->cache->get('data') === false) {
            $this->prepareEstateSync();
        }

        // Initialise status
        if (! $cachedData = $this->cache->get('data')) {

            $this->unlockSync();
            Logger::log('wpis-sync', '[SYNC] ERROR | The cache is empty.');

            return [
                'success'           => false,
                'locked'            => false,
                'message'           => 'The cache is empty.',
            ];
        }

        if (! isset($cachedData['estates']) && ! is_array($cachedData['estates'])) {

            $this->unlockSync();
            Logger::log('wpis-sync', '[SYNC] ERROR | Estates cached are corrupted : ' . json_encode($cachedData));

            return [
                'success'           => false,
                'locked'            => false,
                'message'           => 'Estates cached are corrupted : ' . json_encode($cachedData),
            ];
        }

        $estateImported = 0;
        $totalEstates = count($cachedData['estates']);
        $startTime = microtime(true);
        $isComplete = false;

        // Count how many estates are imported
        foreach ($cachedData['estates'] as $index => $estate) {
            if ($estate['imported']) {
                $estateImported++;
            }
        }

        // Determine estate to import or update
        $targetEstate = null;
        $targetIndex = null;
        foreach ($cachedData['estates'] as $index => $estate) {
            if (!$estate['imported']) {
                $targetIndex = $index;
                $targetEstate = $estate;
                break;
            }
        }

        if (! is_null($targetEstate)) {
            // Import or update targeted estate
            $shouldForceImageImport = isset($settings->forceImportImages) ? $settings->forceImportImages : false;
            $origin = isset($settings->origin) ? $settings->origin : 'admin';

            if (! $lastImport = $this->processEstate($targetEstate['data'], false, $shouldForceImageImport, $origin)) {
                $this->unlockSync();
                return [
                    'success' => false,
                    'message' => 'An error occured while importing estates : ' . json_encode($lastImport)
                ];
            }

            // Update cache data
            $targetEstate['imported'] = true;
            $targetEstate['locked'] = false;
            $estateImported++;
            $cachedData['estates'][$targetIndex] = $targetEstate;

            $this->cache->set('data', $cachedData);
        } else {
            $isComplete = true;
            $lastImport = null;
        }

        $this->unlockSync();

        $response = [
            'success'           => true,
            'locked'            => false,
            'isComplete'        => $isComplete,
            'time'              => round(microtime(true) - $startTime, 3),
            'lastImport'        => $lastImport,
            'estateTotal'       => $totalEstates,
            'estateImported'    => $estateImported,
        ];


        return $response;
    }

    public function updateStart(): array
    {

        $start = microtime(true);

        if ($this->isSyncLocked()) {
            return [
                'success' => false,
                'message' => 'An import is currently running',
            ];
        }

        $this->lockSync();
        $this->prepareEstateSync();

        $data = $this->cache->get('data');

        if (is_null($data['estates'])) {
            $elapsedTime = round(microtime(true) - $start, 3);
            Logger::log('wpis-sync', '[SYNC] ERROR | Estates not found');
            return [
                'success' => false,
                'message' => 'An error occured while reading cache.',
                'time' => $elapsedTime,
            ];
        }

        $progress = [
            'current' => 0,
            'total' => count($data['estates']),
            'isProcessing' => true
        ];

        foreach ($data['estates'] as $index => $estate) {
            if ($this->processEstate($estate['data'], true)) {
                $progress['current'] = $progress['current'] + 1;
                $this->cache->set('progress', $progress);
            }
        }

        $progress['isProcessing'] = false;

        $this->cache->set('progress', $progress);
        $this->cache->delete('data');

        $elapsedTime = round(microtime(true) - $start, 3);

        $this->unlockSync();
        Logger::log('wpis-sync', '[SYNC] UPDATE | Estates Updated: ' . $progress['current'] . ' | Duration: ' . $elapsedTime);

        return [
            'success' => true,
            'time' => $elapsedTime,
            'estatesUpdatedCount' => $progress['current']
        ];
    }

    public function updateProgress()
    {

        $progress = $this->cache->get('progress');

        if ($progress) {
            if (!$progress['isProcessing'] || $progress['current'] === $progress['total']) {
                $this->cache->delete('progress');
            }
        }

        return $progress ?: ['isProcessing' => false];
    }

    public function processEstate($estate, $isUpdateOnly = false, $shouldForceImageImport = false)
    {

        $start = microtime(true);
        $postFound = $this->wpdb->get_results("SELECT post_id FROM " . $this->wpdb->prefix . "postmeta WHERE meta_key = 'wpis_id' AND meta_value = '" . $estate->id . "' LIMIT 1");

        $defaultLanguage = $estate->properties->language;
        $hasMultilingual = false;

        if (isset($estate->properties->multilanguage)) {
            $hasMultilingual = $estate->properties->multilanguage;
        } elseif (isset($estate->properties->multilingual)) {
            $hasMultilingual = $estate->properties->multilingual;
        }

        $estateName = $hasMultilingual ? $estate->name->{$defaultLanguage} : $estate->name;
        $estateDesc = $hasMultilingual ? $estate->description->base->{$defaultLanguage} : $estate->description->base;

        // Update phase
        if (!empty($postFound)) {

            $postId = $postFound[0]->post_id;

            if ($this->isEstateLocked($postId)) {
                $state = self::SYNC_SKIP;
                if ($postId) {
                    Logger::log('wpis-sync', '[SYNC] ' . $state . ' | Post Id: ' . $postId . ' | Estate ID: ' . $estate->id . ' | Images: ' . count($estate->pictures) . ' | Duration: ' . round((microtime(true) - $start), 3));
                }

                return [
                    'postId' => $postId,
                    'estateId' => $estate->id,
                    'estateName' => $estateName,
                ];
            };

            if ($this->hasEstateHashChanged($postId, $estate)) {
                $state = self::SYNC_UPDATE;
                $post = [
                    'ID'           => $postId,
                    'post_title'   => $estateName,
                    'post_content' => $estateDesc,
                ];

                \wp_update_post($post);

                if ($shouldForceImageImport) {
                    $this->processPictures($estate, $postId);
                }
            } else {
                $state = self::SYNC_SKIP;
            }
        }
        // Create phase
        elseif (!$isUpdateOnly) {
            $state = self::SYNC_CREATE;
            $postId = \wp_insert_post([
                'post_type'      => $this->postTypeEstateName,
                'post_name'      => $estate->id . '-' . $estateName,
                'post_title'     => $estateName,
                'post_content'   => $estateDesc ?? " ",
                'post_status'    => 'publish',
                'comment_status' => 'closed',
                'ping_status'    => 'closed'
            ]);

            $this->addMetaToQueue(PostTypesManager::$postTypeMetaPrefix . '_locked',  0);
            $this->addMetaToQueue(PostTypesManager::$postTypeMetaPrefix . '_id',  $estate->id);

            $this->processPictures($estate, $postId);
        } else {
            return false;
        }

        // Map estate data as post meta
        if ($this->hasEstateHashChanged($postId, $estate) || $state === self::SYNC_CREATE) {
            foreach (PostTypesManager::$postTypeMetas as $group) {
                foreach ($group['fields'] as $property) {

                    $propertySegments = explode('.', $property['reference']);
                    $postMetaName = PostTypesManager::$postTypeMetaPrefix . '_' . str_replace('.', '_', $property['reference']);
                    $translatable = $property['translatable'] ?? false;

                    if (property_exists($estate, $propertySegments[0])) {

                        if (count($propertySegments) > 1) {
                            if (is_object($estate->{$propertySegments[0]}) && property_exists($estate->{$propertySegments[0]}, $propertySegments[1])) {
                                $value = $estate->{$propertySegments[0]}->{$propertySegments[1]};
                            }
                        } else {
                            $value = $estate->{$propertySegments[0]};
                        }

                        if ($translatable) {
                            if ($hasMultilingual) {
                                if (isset($value->{$defaultLanguage})) {
                                    $this->addMetaToQueue($postMetaName, $value->{$defaultLanguage});
                                }
                                foreach (PostTypesManager::$supportedLanguages as $language) {
                                    $this->addMetaToQueue($postMetaName . '_' . $language,  $value->{$language} ?? '');
                                }
                            } else {
                                foreach (PostTypesManager::$supportedLanguages as $language) {
                                    \delete_post_meta($postId, $postMetaName . '_' . $language);
                                }
                                $this->addMetaToQueue($postMetaName, $value);
                            }
                        } else {
                            $this->addMetaToQueue($postMetaName, $value);
                        }
                    }
                }
            }
        }

        // Generate data hash
        $this->addMetaToQueue(PostTypesManager::$postTypeMetaPrefix . '_dataHash', md5(json_encode($estate)));

        // Generate pictures hash
        $currentPicturesHash = md5(json_encode($estate->pictures));
        $storedPicturesHash = \get_post_meta($postId, PostTypesManager::$postTypeMetaPrefix . '_picturesHash', true);
        $this->addMetaToQueue(PostTypesManager::$postTypeMetaPrefix . '_picturesTemp', json_encode($estate->pictures));

        if (\get_post_meta($postId, PostTypesManager::$postTypeMetaPrefix . '_picturesHashChanged', true) !== '1') {
            $hasPictureHashChanged = $storedPicturesHash && $currentPicturesHash !== $storedPicturesHash;
            $this->addMetaToQueue(PostTypesManager::$postTypeMetaPrefix . '_picturesHashChanged', $hasPictureHashChanged);
        }
        $this->addMetaToQueue(PostTypesManager::$postTypeMetaPrefix . '_picturesHash', $currentPicturesHash);

        // Handle various mapping
        $this->handleEstateTaxonomiesMapping($postId);
        $this->handleProjectsMapping($postId);
        $this->handleAgentsMapping($postId);
        $this->handlePostUpdate($postId);

        // Set last update date time
        $now = new DateTime('now');
        $dateTime = [
            'date' => $now->format('d/m/Y'),
            'time' => $now->format('H:i:s')
        ];
        \update_option('wpis_last_update', $dateTime, false);

        // Process meta queue
        $this->addMetaToQueue(PostTypesManager::$postTypeMetaPrefix . '_completed',  1);
        $this->processMetaQueue($postId);

        if ($postId) {
            Logger::log('wpis-sync', '[SYNC] ' . $state . ' | Post ID: ' . $postId . ' | Estate ID: ' . $estate->id . ' | Images: ' . count($estate->pictures) . ' | Duration: ' . round((microtime(true) - $start), 3));
        }

        return [
            'postId' => $postId,
            'estateId' => $estate->id,
            'estateName' => $estateName
        ];
    }

    public function processPictures($estate, $postId)
    {

        $wpUploadDir = \wp_upload_dir();
        $hasPostThumbnail = false;

        if ($wpUploadDir['error']) {
            return $wpUploadDir['error'];
        }

        foreach ($estate->pictures as $index => $picture) {

            $filename   = $estate->id . '_' . $picture->id . '.' . $picture->format;
            $realpath   = $wpUploadDir['path'] . '/' . $filename;
            $url        = $wpUploadDir['url'] . '/' . $filename;

            $url = isset($picture->urls) ? $picture->urls->large : $picture->url;

            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_HEADER, false);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

            $data = curl_exec($ch);
            $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            curl_close($ch);

            if ($code === 200) {
                $result = file_put_contents($realpath, $data);

                $uploadId = \wp_insert_attachment([
                    'guid'           => $realpath,
                    'post_mime_type' => mime_content_type($realpath),
                    'post_title'     => $filename,
                    'post_content'   => '',
                    'post_status'    => 'inherit',
                    'menu_order'     => $index,
                ], $realpath,  $postId, true);

                if (\is_wp_error($uploadId)) {
                    Logger::log('wpis-sync', [
                        '[SYNC] ERROR | IMAGE INSERT ATTACHMENT',
                        '[SYNC] > URL      : ' . $url,
                        '[SYNC] > REALPATH : ' . $realpath,
                        '[SYNC] > POST_ID  : ' . $postId,
                        '[SYNC] > WP_ERROR : ' . json_encode($uploadId)
                    ]);
                }

                if ($this->debug) {
                    \wp_update_attachment_metadata($uploadId, \wp_generate_attachment_metadata($uploadId, $realpath));
                } else {
                    // phpcs:ignore WordPress.PHP.NoSilencedErrors -- Silencing notice and warning is intentional. See https://core.trac.wordpress.org/ticket/42480
                    @\wp_update_attachment_metadata($uploadId, \wp_generate_attachment_metadata($uploadId, $realpath));
                }

                if (!$hasPostThumbnail) {
                    \set_post_thumbnail($postId, $uploadId);
                    $hasPostThumbnail = true;
                }
            } else {
                Logger::log('wpis-sync', [
                    '[SYNC] ERROR | IMAGE IMPORT : ' . $code . ' ' . $data,
                    '[SYNC] > URL : ' . $url,
                    '[SYNC] > CODE : ' . $code,
                    '[SYNC] > RESPONSE : ' . $data
                ]);
            }
        }

        return 'success';
    }

    public function removeEstates($restrictToImageChanges = false): array
    {
        $syncReport = $this->prepareEstateSync();

        if (isset($syncReport['success']) && !$syncReport['success']) {
            return $syncReport;
        }

        $i = 0;

        $estateToRemove = $syncReport['estatesToRemove'];

        foreach ($syncReport['estatesToRemove'] as $index => $estate) {

            if ($restrictToImageChanges && !$estate->picturesHashChanged) {
                unset($estateToRemove[$estate->estateId]);
                continue;
            } else if ($this->enablePartialRemove) {
                $this->removeEstateImagesExceptThumbnail($estate->postId);
                $i++;
            } else {
                $attachments = get_attached_media('', $estate->postId);
                foreach ($attachments as $attachment) {
                    wp_delete_attachment($attachment->ID, true);
                }
                wp_delete_post($estate->postId, true);
                $i++;
            }
        }

        return [
            'success' => true,
            'estatesRemoved' => $i,
            'estatesRemovedList' => $estateToRemove
        ];
    }

    private function removeEstateImagesExceptThumbnail($postId): void
    {
        $attachments = get_attached_media('image', $postId);

        if (empty($attachments)) {
            return;
        }

        $thumbnailId = (int) get_post_thumbnail_id($postId);

        if ($thumbnailId <= 0) {
            $firstAttachment = reset($attachments);
            if ($firstAttachment && isset($firstAttachment->ID)) {
                $thumbnailId = (int) $firstAttachment->ID;
                set_post_thumbnail($postId, $thumbnailId);
            }
        }

        foreach ($attachments as $attachment) {
            if ((int) $attachment->ID === $thumbnailId) {
                continue;
            }

            wp_delete_attachment($attachment->ID, true);
        }
        update_post_meta($postId, PostTypesManager::$postTypeMetaPrefix . '_soft_deleted', true);
    }

    public function removeAll(): array
    {

        $query = new \WP_Query([
            'post_type' => [$this->postTypeEstateName, $this->postTypeProjectName],
            'meta_key' => 'wpis_id',
            'posts_per_page' => 1000
        ]);

        $i = 0;

        if (!$query->have_posts()) {
            return [
                'success' => false,
                'message' => 'There is no estate to remove.'
            ];
        }

        if ($this->enableTaxonomies) {
            foreach (['estate_cities', 'estate_categories', 'estate_purposes', 'estate_ammeneties'] as $taxonomy) {
                $terms = get_terms($taxonomy, ['fields' => 'ids', 'hide_empty' => false]);
                foreach ($terms as $value) {
                    wp_delete_term($value, $taxonomy);
                }
            }
        }

        foreach ($query->posts as $post) {
            $attachments = get_attached_media('', $post->ID);
            foreach ($attachments as $attachment) {
                wp_delete_attachment($attachment->ID, true);
            }
            wp_delete_post($post->ID, true);
            $i++;
        }

        return [
            'success' => true,
            'estatesRemoved' => $i,
        ];
    }

    private function handleEstateTaxonomiesMapping($postId): void
    {

        if (!$this->enableTaxonomies) {
            return;
        }

        // > estate_purposes
        $estatePurposeLabel = get_post_meta($postId, 'wpis_purpose_label', true);
        if (! $term = term_exists($estatePurposeLabel, 'estate_purposes')) {
            $term = wp_insert_term($estatePurposeLabel, 'estate_purposes');
        }
        if (!is_wp_error($term)) {
            wp_set_post_terms($postId, $term['term_id'], 'estate_purposes');
        }

        // > estate_cities
        $estateCity = get_post_meta($postId, 'wpis_address_city', true);
        if (! $term = term_exists($estateCity, 'estate_cities')) {
            $term = wp_insert_term($estateCity, 'estate_cities');
        }
        if (!is_wp_error($term)) {
            wp_set_post_terms($postId, $term['term_id'], 'estate_cities');
        }

        // > estate_categories
        $estateCategory = get_post_meta($postId, 'wpis_category_label', true);
        if (! $term = term_exists($estateCategory, 'estate_categories')) {
            $term = wp_insert_term(ucfirst($estateCategory), 'estate_categories');
        }
        if (!is_wp_error($term)) {
            wp_set_post_terms($postId, $term['term_id'], 'estate_categories');
        }

        // > estate_amenities
        $estateAmenitiesGeneric = get_post_meta($postId, 'wpis_amenities_generic', true);
        $estateAmenitiesCustom = get_post_meta($postId, 'wpis_amenities_custom', true);

        $estateAmenitiesGeneric = is_array($estateAmenitiesGeneric) ? $estateAmenitiesGeneric : [];
        $estateAmenitiesCustom = is_array($estateAmenitiesCustom) ? $estateAmenitiesCustom : [];

        $estateAmenities = array_merge($estateAmenitiesGeneric, $estateAmenitiesCustom);
        $amenitiesTermIds = [];

        foreach ($estateAmenities as $ammenety) {
            if (! $term = term_exists($ammenety, 'estate_ammeneties')) {
                $term = wp_insert_term($ammenety, 'estate_ammeneties');
            }
            if (!is_wp_error($term)) {
                $amenitiesTermIds[] = (int) $term['term_id'];
            }
        }

        wp_set_post_terms($postId, $amenitiesTermIds, 'estate_ammeneties');
    }

    private function handleProjectsMapping($postId): void
    {
        if (! $this->enableProjectManagement) {
            return;
        }

        if (! get_post_meta($postId, 'wpis_properties_hasChildren', true)) {
            return;
        }

        wp_update_post(['ID' => $postId, 'post_type' => $this->postTypeProjectName]);

        $estateId = get_post_meta($postId, 'wpis_id', true);
        $childPosts = query_posts([
            'post_type' => $this->postTypeEstateName,
            'posts_per_page' => -1,
            'meta_query' => [
                [
                    'key'     => PostTypesManager::$postTypeMetaPrefix . '_' . 'properties_parentId',
                    'compare' => '=',
                    'value'   => $estateId,
                    'type'    => 'CHAR',
                ]
            ]
        ]);

        foreach ($childPosts as $childPost) {
            wp_update_post(['ID' => $childPost->ID, 'post_parent' => $postId]);
        }
    }

    private function handleAgentsMapping($postId): void
    {
        // TO DO: TEST REFACTORING
        if (! $this->enableAgentManagement) {
            return;
        }

        $fields = [
            'wpis_agent_firstname',
            'wpis_agent_lastname',
            'wpis_agent_picture',
            'wpis_agent_email',
            'wpis_agent_phone',
        ];

        $agentMeta = [];
        foreach ($fields as $field) {
            $agentMeta[$field] = get_post_meta($postId, $field, true);
        }

        if (empty($agentMeta['wpis_agent_firstname']) || empty($agentMeta['wpis_agent_lastname'])) {
            return;
        }

        $queryArgs = [
            'post_type'   => $this->postTypeAgentName,
            'post_status' => 'any',
            'meta_query'  => [
                'relation' => 'AND',
                [
                    'key'   => 'wpis_agent_firstname',
                    'value' => $agentMeta['wpis_agent_firstname'],
                ],
                [
                    'key'   => 'wpis_agent_lastname',
                    'value' => $agentMeta['wpis_agent_lastname'],
                ]
            ],
            'posts_per_page' => 1,
        ];

        $existingPost = get_posts($queryArgs);
        $agentPostId = !empty($existingPost) ? $existingPost[0]->ID : 0;

        $agentPostId = wp_insert_post([
            'ID'         => $agentPostId,
            'post_title' => $agentMeta['wpis_agent_firstname'] . ' ' . $agentMeta['wpis_agent_lastname'],
            'post_status' => 'publish',
            'post_type'  => $this->postTypeAgentName
        ]);

        foreach ($agentMeta as $key => $value) {
            update_post_meta($agentPostId, $key, $value);
        }
    }

    private function handlePostUpdate($postId): void
    {
        $updateScriptPath = get_stylesheet_directory() . '/wpis-post-update.php';

        if (!file_exists($updateScriptPath)) {
            return;
        }

        require_once $updateScriptPath;

        if (!function_exists('wpis_post_update')) {
            return;
        }

        wpis_post_update($postId);
    }

    private function addMetaToQueue($key, $value): void
    {
        $this->metaStack[$key] = $value;
    }

    private function processMetaQueue($postId): void
    {
        $post = [
            'ID' => $postId,
            'meta_input' => $this->metaStack
        ];
        wp_update_post($post);
        $this->metaStack = [];
    }

    private function isEstateLocked($postId): bool
    {
        return get_post_meta($postId, PostTypesManager::$postTypeMetaPrefix . '_locked', true) === '1';
    }

    private function hasEstateHashChanged($postId, $estate): bool
    {

        // $redis = $this->getRedisInstance();
        // if($redis){
        //     // $redisKey = 'estate_' . $postId;
        //     // $redisHash = $redis->hGet($redisKey, 'dataHash');
        //     // if($redisHash){
        //     //     return $redisHash !== md5(json_encode($estate));
        //     // }
        // }

        $storedDataHash = get_post_meta($postId, PostTypesManager::$postTypeMetaPrefix . '_dataHash', true);
        $currentDataHash = md5(json_encode($estate));
        return $storedDataHash !== $currentDataHash;
    }

    private function isSyncLocked(): bool
    {

        if ($this->debug) {
            return false;
        }

        if (file_exists($this->lockFile)) {

            if ((new \DateTime())->getTimestamp() - filemtime($this->lockFile) >= $this->expiredLockTime) {
                Logger::log('wpis-sync', '[SYNC] Lock file removed after exceeding lifetime.');
                $this->unlockSync();
                return $this->isSyncLocked();
            }

            return true;
        }

        return false;
    }

    private function lockSync(): bool
    {

        if ($this->debug) {
            return false;
        }

        touch($this->lockFile);

        return true;
    }

    private function unlockSync(): bool
    {

        if ($this->debug) {
            return true;
        }

        if (file_exists($this->lockFile)) {
            unlink($this->lockFile);
        }

        return true;
    }

    // TODO: Handle Redis Support
    // private function getRedisInstance() : ?Redis {
    //     if( ! class_exists('Redis') ){
    //         return null;
    //     }

    //     try {
    //         $redis = new Redis();
    //         $redis->connect('127.0.0.1', 6379, 1.0); // Adjust host/port/timeouts as needed

    //         if (!$redis->ping()) {
    //             return null;
    //         }

    //         return $redis;
    //     } catch (Exception $e) {
    //         return null;
    //     }
    // }
}
