/home/ivoiecob/www/wp-content/plugins/wp-social-reviews/app/Models/Review.php
<?php

namespace WPSocialReviews\App\Models;

use WPSocialReviews\App\Services\Platforms\PlatformErrorManager;
use WPSocialReviews\App\Services\Platforms\Reviews\Helper;
use WPSocialReviews\Framework\Support\Arr;
use WPSocialReviews\App\Models\Traits\SearchableScope;

class Review extends Model
{
    use SearchableScope;
    protected $table = 'wpsr_reviews';

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $casts = [
        'rating' => 'integer',
        'fields' => 'json'
    ];

    protected $fillable = [
        'fields',
        'platform_name',
        'review_title',
        'reviewer_name',
        'reviewer_text',
        'review_time',
        'review_id',
        'reviewer_id',
        'rating',
        'reviewer_url',
        'reviewer_img',
        'review_approved',
        'recommendation_type',
        'source_id',
	    'category'
    ];

    /**
     * $searchable Columns in table to search
     * @var array
     */
    protected $searchable = [
        'id',
        'platform_name',
        'review_title',
        'reviewer_name',
        'reviewer_text',
        'review_time',
        'rating',
	    'category'
    ];

    public static function getErrors($platforms)
    {
        $errors = [];

        if(!empty($platforms)){
            foreach ($platforms as $platform){
                $errors = (new PlatformErrorManager($platform))->getFrontEndErrors();
            }
        }

        return $errors;
    }

    public static function collectReviewsAndBusinessInfo($settings = array(), $templateId = null)
    {
        $selectedBusinesses = Arr::get($settings, 'selectedBusinesses', []);
        $platforms       = Arr::get($settings, 'platform', []);

        $activePlatforms = apply_filters('wpsocialreviews/available_valid_reviews_platforms', ['testimonial' => 'Testimonial']);

        if (empty($platforms)) {
            $platforms = array();
        }
        $customValidPlatforms = get_option('wpsr_available_valid_platforms', []);
        if(!empty($customValidPlatforms)){
            $activePlatforms = array_merge($activePlatforms, $customValidPlatforms);
        }

        $validTemplatePlatforms = array_intersect($platforms, array_keys($activePlatforms));

        //add custom with platforms if custom reviews exists
        // Cache this check to avoid repeated queries
        static $customReviewsCache = null;
        if ($customReviewsCache === null) {
            $customReviewsCache = self::where('platform_name', 'custom')->exists();
        }
        if ($customReviewsCache && in_array("custom", $platforms)) {
            $index                          = array_search('custom', $platforms);
            $validTemplatePlatforms[$index] = 'custom';
        }

        $filteredReviews = array();
        $allReviews      = array();
        $businessInfo    = array();

        if (!empty($validTemplatePlatforms)) {
            $filteredReviews = self::filteredReviewsQuery($validTemplatePlatforms, $settings)->get();
	        // Check if this is an initial load for editor with load more enabled
	        $isEditorInitialLoad = Arr::get($settings, 'is_editor_initial_load', false);
	        if ($isEditorInitialLoad) {
		        $totalReviewsNumber = Arr::get($settings, 'totalReviewsNumber.desktop', 50);
		        $allReviews = self::whereIn('platform_name', $validTemplatePlatforms)->limit($totalReviewsNumber)->get();
	        } else {
		        $allReviews = self::whereIn('platform_name', $validTemplatePlatforms)->get();
	        }

            $businessInfo    = Helper::getSelectedBusinessInfoByPlatforms($validTemplatePlatforms, $selectedBusinesses);
        }

        if(defined('WC_PLUGIN_FILE')){
            $filteredReviews = Helper::trimProductTitle($filteredReviews);
        }

        return array(
            'filtered_reviews' => $filteredReviews,
            'all_reviews'      => $allReviews,
            'business_info'    => $businessInfo,
            'errors'           => self::getErrors($validTemplatePlatforms)
        );
    }

    //all filtered reviews
    public static function filteredReviewsQuery($platforms, $filters)
    {
        $includeIds = Arr::get($filters, 'selectedIncList', []);
        $excludeIds = Arr::get($filters, 'selectedExcList', []);

        $starFilterVal = Arr::get($filters, 'starFilterVal', -1);
        $filterByTitle = Arr::get($filters, 'filterByTitle', 'all');
        $order         = Arr::get($filters, 'order', 'desc');
        $hideEmptyReviews = Arr::get($filters, 'hide_empty_reviews', false);
        $selectedBusinesses     = Arr::get($filters, 'selectedBusinesses', array());

		$categories = Arr::get($filters, 'selectedCategories', array());
	    $platformsWithCategories = array();

		if (count($categories)) {
			// Get platforms that support categories from Helper
			$helperPlatformsWithCategories = Helper::getPlatformsWithCategories();
			
			// Get custom/valid platforms - these should also support categories
			$validPlatforms = get_option('wpsr_available_valid_platforms', []);
			$validPlatformKeys = !empty($validPlatforms) ? array_keys($validPlatforms) : [];
			
			// Combine both standard platforms and custom platforms that support categories
			$allPlatformsWithCategories = array_merge($helperPlatformsWithCategories, $validPlatformKeys);
			
			// Find which selected platforms support categories
			$platformsWithCategories = array_intersect($platforms, $allPlatformsWithCategories);
			
			// Remove platforms with categories from the main platforms array
			$platforms = array_diff($platforms, $platformsWithCategories);
		}

        $reviews = self::whereIn('platform_name', $platforms);

        $has_column = Helper::hasReviewApproved();
        if($has_column) {
            $reviews = $reviews->where('review_approved', '1');
        }

	    if (count($platformsWithCategories)) {
		    $reviews->orWhere(function ($query) use ($platformsWithCategories, $categories) {
			    $query->whereIn('platform_name', $platformsWithCategories)->whereIn('category', $categories);
		    });
	    }

        if ($order === 'random' ) {
            if($filters['pagination_type'] === 'none') {
                $reviews = $reviews->inRandomOrder();
            }
            else {
                $reviews = $reviews->inRandomOrder('1234');
            }
        } else {
	        $reviews = $reviews->orderBy('review_time', $order);
        }

        //filter by star rating
        if($starFilterVal !== -1) {
            $reviews = $reviews->where('rating', '>=', $filters['starFilterVal']);
        }

        //filter by empty reviews
        if($hideEmptyReviews) {
            $reviews->where('reviewer_text', '!=', '');
        }

        //filter by included or excluded
        if ($filterByTitle === 'include' && count($includeIds)) {
            $includeIds = array_unique(array_map('intval', $includeIds));
            // Chunk large arrays to avoid very long IN clauses
            $chunkSize = 100;
            if (count($includeIds) <= $chunkSize) {
                $reviews = $reviews->whereIn('id', $includeIds);
            } else {
                $reviews->where(function($query) use ($includeIds, $chunkSize) {
                    $chunks = array_chunk($includeIds, $chunkSize);
                    $firstChunk = true;
                    foreach ($chunks as $chunk) {
                        if ($firstChunk) {
                            $query->whereIn('id', $chunk);
                            $firstChunk = false;
                        } else {
                            $query->orWhereIn('id', $chunk);
                        }
                    }
                });
            }
        }
        if ($filterByTitle === 'exclude' && count($excludeIds)) {
            $excludeIds = array_unique(array_map('intval', $excludeIds));
            // Chunk large arrays to avoid very long NOT IN clauses
            $chunkSize = 100;
            if (count($excludeIds) <= $chunkSize) {
                $reviews = $reviews->whereNotIn('id', $excludeIds);
            } else {
                // For NOT IN with chunks, we need to use whereNotIn for each chunk
                // This is less efficient but necessary for very large arrays
                foreach (array_chunk($excludeIds, $chunkSize) as $chunk) {
                    $reviews = $reviews->whereNotIn('id', $chunk);
                }
            }
        }

        if(!empty($selectedBusinesses)) {
            // Chunk large arrays to avoid very long IN clauses which are slow
            // MySQL has a limit on the number of items in an IN clause, and large IN clauses are slow
            $chunkSize = 100; // Optimal chunk size for MySQL performance
            $selectedBusinesses = array_unique($selectedBusinesses);
            
            if (count($selectedBusinesses) <= $chunkSize) {
                // Small array, use single query
                $reviews = $reviews->whereIn('source_id', $selectedBusinesses);
            } else {
                // Large array, use chunked queries with OR conditions
                $reviews->where(function($query) use ($selectedBusinesses, $chunkSize) {
                    $chunks = array_chunk($selectedBusinesses, $chunkSize);
                    $firstChunk = true;
                    foreach ($chunks as $chunk) {
                        if ($firstChunk) {
                            $query->whereIn('source_id', $chunk);
                            $firstChunk = false;
                        } else {
                            $query->orWhereIn('source_id', $chunk);
                        }
                    }
                });
            }
        }

        //filter by words
        $reviews = static::filterReviewsByWords($reviews, $filters);

        //filtered by total reviews
        $totalReviews = Arr::get($filters, 'totalReviewsNumber');
        $numOfReviews = wp_is_mobile() ? Arr::get($totalReviews, 'mobile') : Arr::get($totalReviews, 'desktop');
        // Detect device type and override query if needed
        $numOfReviews = apply_filters('wpsocialreviews/responsive_post_count', $numOfReviews, $totalReviews);

        // Check if this is an initial load for editor with load more enabled
        $isEditorInitialLoad = Arr::get($filters, 'is_editor_initial_load', false);

        if ($isEditorInitialLoad) {
            // For include/exclude (hand-pick reviews), load all based on Number of Reviews setting
            // For normal pagination, limit to paginate value
            if ($filterByTitle === 'include' || $filterByTitle === 'exclude') {
                // When using include/exclude, load all reviews based on Number of Reviews setting
                if ($numOfReviews > 0) {
                    $reviews = $reviews->limit((int)$numOfReviews);
                }
                // If numOfReviews is not set, load all selected reviews (no limit)
            } else {
                // Normal pagination behavior - limit to paginate value for load more
                $paginateNumber = Arr::get($filters, 'paginate_number');
                $fallbackPaginate = (int) Arr::get($filters, 'paginate', 6);
                $paginate = wp_is_mobile() ? (int) Arr::get($paginateNumber, 'mobile', $fallbackPaginate) : (int) Arr::get($paginateNumber, 'desktop', $fallbackPaginate);

                $reviews = $reviews->limit($paginate);
            }
        } elseif($numOfReviews > 0) {
            $reviews = $reviews->limit((int)$numOfReviews);
        }

//        $multiBusinessInfo = Helper::getBusinessInfoByPlatforms($platforms);

//        $freq = array();
//        foreach ($selectedBusinesses as $businessId) {
//            $platform = Arr::get($multi_business_info, 'platforms.' . $businessId . '.platform_name', '');
//            if (!empty($platform)) {
//                if (isset($freq[$platform])) {
//                    $freq[$platform]++;
//                } else {
//                    $freq[$platform] = 1;
//                }
//            }
//            else {
//                unset($selectedBusinesses[$businessId]);
//            }
//        }

//        $selected = [];
//        $notSelected = [];
//        foreach ($platforms as $platform) {
//            if(isset($freq[$platform]) && $freq[$platform]>=1) {
//                $selected[] = $platform;
//            } else {
//                $notSelected[] = $platform;
//            }
//        }

//        if(!empty($notSelected)) {
//            $query1 = $reviews->where('platform_name', $notSelected);
//        }
//
//        if(!empty($selectedBusinesses)) {
//            $query2 = $reviews->where('source_id', $selectedBusinesses);
//        }


        return $reviews;
    }

    public static function filterReviewsByWords($reviews, $filters)
    {
        //filter by words
        $includesWords = $excludesWords = [];
        if (!empty($filters['includes_inputs'])) {
            $includesWords = array_map('trim', explode(",", $filters['includes_inputs']));
        }

        //only have excludes inputs
        if (!empty($filters['excludes_inputs'])) {
            $excludesWords = array_map('trim', explode(",", $filters['excludes_inputs']));
        }

        $existsInBoth = array_intersect($includesWords, $excludesWords);
        foreach($existsInBoth as $word) {
            if(in_array($word, $includesWords) && in_array($word, $excludesWords)) {
                $includesWords = array_diff($includesWords, [$word]);
                $excludesWords = array_diff($excludesWords, [$word]);
            }
        }

        $excludesWords = array_merge($excludesWords, $existsInBoth);
        $includesWords = array_merge($includesWords, []);

        if (!empty($includesWords)) {
            $reviews->where(function ($query) use ($includesWords) {
                foreach($includesWords as $word) {
                    $query->orWhere('reviewer_text', 'like', '%'.$word.'%');
                }
            });
        }

        //only have excludes inputs
        if (!empty($excludesWords)) {
            $reviews->where(function ($query) use ($excludesWords) {
                foreach($excludesWords as $word) {
                    $query->where('reviewer_text', 'not like', '%'.$word.'%');
                }
            });
        }
        return $reviews;
    }

    public static function paginatedReviews($platforms, $filters = array(), $page = 1)
    {
        // Get responsive paginate value
        $paginateNumber = Arr::get($filters, 'paginate_number');
        $fallbackPaginate = (int) Arr::get($filters, 'paginate', 6);
        $paginate = wp_is_mobile() ? (int) Arr::get($paginateNumber, 'mobile', $fallbackPaginate) : (int) Arr::get($paginateNumber, 'desktop', $fallbackPaginate);
        
        $offset   = ($paginate * $page) - $paginate;

        $paginationType = Arr::get($filters, 'pagination_type', '');
        $templateType   = Arr::get($filters, 'templateType', 'grid');

        // Get the filtered query with all conditions applied
        $filterReviewsQuery = self::filteredReviewsQuery($platforms, $filters);
        
        // Use SQL COUNT instead of fetching all rows - much more efficient
        // Clone the query before applying count to preserve the original for later use
        $countQuery = clone $filterReviewsQuery;
        $totalFilterReviews = $countQuery->count();

        // activate pagination
        if ($paginationType === 'load_more' && $templateType !== 'slider') {
            if ($totalFilterReviews > 0) {
                if ($totalFilterReviews < $paginate) {
                    $paginate = $totalFilterReviews;
                } else {
                    $reviewsNow = $page * $paginate;
                    if ($reviewsNow > $totalFilterReviews) {
                        $extraReviews = ($reviewsNow - $totalFilterReviews);
                        $paginate     = $paginate - $extraReviews;
                    }
                }
            }

            $filterReviewsQuery = $filterReviewsQuery->offset($offset)
                                                     ->limit($paginate);
        }

        $reviews = $filterReviewsQuery->get();

        return array(
            'total_reviews' => $totalFilterReviews,
            'reviews'       => $reviews,
            'errors'        => self::getErrors($platforms)
        );
    }

    public static function modifyIncludeAndExclude($templateMeta, $reviewsData)
    {
        if (isset($templateMeta['filterByTitle']) && $templateMeta['filterByTitle'] !== 'all' && !empty($reviewsData['filtered_reviews'])) {
            $reviewIds = array_column(json_decode($reviewsData['all_reviews'], true),"id");
            $reviewsLists = array();
            if ($templateMeta['filterByTitle'] === 'include') {
                $reviewsLists = $templateMeta['selectedIncList'];
                $commonLists = array_intersect($reviewsLists,$reviewIds);
                $templateMeta['selectedIncList'] = $commonLists;
            } else if ($templateMeta['filterByTitle'] === 'exclude') {
                $reviewsLists = $templateMeta['selectedExcList'];
                $commonLists = array_intersect($reviewsLists,$reviewIds);
                $templateMeta['selectedExcList'] = $commonLists;
            }
        } else {
            $templateMeta['selectedExcList'] = [];
            $templateMeta['selectedIncList'] = [];
        }

        return $templateMeta;
    }

    public static function formatBusinessInfo($reviewsData)
    {
        $platforms_data = array();
        $platforms = Arr::get($reviewsData, 'business_info.platforms', []);
        if(!empty($platforms)) {
            foreach ($platforms as $key => $info) {
                $platforms_data[$key] = $info;
            }
            $reviewsData['business_info']['platforms'] = $platforms_data;
        }
        return $reviewsData['business_info'];
    }

    public static function getInternalBusinessInfo($platform, $dataSource = [])
    {
        $source_id = Arr::get($dataSource, 'source_id', null);
        $handle = Arr::get($dataSource, 'handle', '');
        $isImported = Arr::get($dataSource, 'is_imported', false);
        $sourceId = $source_id ? $source_id : $platform;

        $existingInfos = get_option('wpsr_reviews_'.$platform.'_business_info', []);
        $reviewsQuery = static::where('platform_name', $platform)
            ->where('source_id', $sourceId)
            ->where('review_approved', 1);
        $totalReviews = $reviewsQuery->count();
        $avgRating    = $totalReviews > 0 ? $reviewsQuery->avg('rating') : 0;;


        $businessInfo = array(
            'place_id'          => $sourceId,
            'name'              => $handle ? $handle : Arr::get($existingInfos, $sourceId.'.name', ''),
            'url'               => Arr::get($existingInfos, $sourceId.'.url', ''),
            'logo'              => Arr::get($existingInfos, $sourceId.'.logo', ''),
            'platform_label'    => Arr::get($existingInfos, $sourceId.'.platform_label', ''),
            'privacy_policy_url'=> Arr::get($existingInfos, $sourceId.'.privacy_policy_url', ''),
            'address'           => Arr::get($existingInfos, $sourceId.'.address', ''),
            'average_rating'    => $avgRating,
            'total_rating'      => $totalReviews,
            'phone'             => Arr::get($existingInfos, $sourceId.'.phone', ''),
            'platform_name'     => $platform,
            'is_imported'       => $isImported,
            'status'            => true
        );
        $existingInfos[$sourceId] = $businessInfo;
        return $existingInfos;
    }

	public static function getCategories()
	{
		$categories = static::select('category')->whereNotNull('category')->groupBy('category')->lists('category')->toArray();

		return array_filter($categories);
	}

    public static function trashReview($platform, $uniqueIdentifierKey, $id)
    {
        static::where('platform_name', $platform)
            ->where($uniqueIdentifierKey, $id)
            ->delete();
    }
}