<?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;
    //     }
    // }
}