<?php

namespace App\Services;

use App\Models\BusinessCategory;
use App\Models\Follower;
use App\Models\HashTag;
use App\Models\Product;
use App\Models\SearchHistory;
use App\Models\User;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use ProtoneMedia\LaravelCrossEloquentSearch\Search;

trait SearchHistoryService
{
   /**
    * Store new record
    *
    * @param array $data
    * @return Builder|Model/
    */
   public static function store(array $data): Model|Builder
   {
      return SearchHistory::query()->create($data);
   }

   /**
    * Increase query occurrences
    *
    * @return mixed
    */
   public function increaseQueryOccurrences(): mixed
   {
      $occurrence = $this->{"number_of_occurrences"} + config("torryme.constants.default_increment_or_decrement");
      $this->updateService(['number_of_occurrences' => $occurrence]);

      return $occurrence;
   }

   /**
    * Update existing record
    *
    * @param array $data
    * @return mixed
    */
   public function updateService(array $data): mixed
   {
      return tap($this)->update($data);
   }

   /**
    * Find by id
    *
    * @param int $id
    * @return Builder|Builder[]|Collection|Model|null
    */
   public static function findById(int $id): Model|Collection|Builder|array|null
   {
      return SearchHistory::query()->find($id);
   }

   /**
    * Get search histories
    *
    * @param int|null $pageNumber
    * @return LengthAwarePaginator|Builder[]|Collection
    */
   public static function getHistories(int $pageNumber = null): Collection|LengthAwarePaginator|array
   {
      /** @var User $user */
      $user = auth()->user();

      $queryBuilder =
         SearchHistory::query()
            ->where("user_id", $user->{"id"})
            ->orderByDesc('created_at');

      if ($pageNumber === null) {
         return $queryBuilder->get();
      }

      return $queryBuilder->paginate(
         perPage: config("torryme.constants.items_per_page"),
         page: $pageNumber
      );
   }

   /**
    * Delete search history
    *
    * @param int|null $searchHistoryId
    * @return mixed
    */
   public static function deleteHistory(int $searchHistoryId = null): mixed
   {
      try {
         $result = null;

         /** @var User $user */
         $user = auth()->user();

         if (isset($searchHistoryId)) {
            $searChToDelete = self::findById($searchHistoryId);
            $result = $searChToDelete->delete();
         } else {
            $user->userSearch()->delete();
         }
      } catch (\Exception $exception) {
         log_debug(exception: $exception, prefix: 'SearchHistoryService::deleteHistory');
      }

      return $result;
   }

   /**
    * Add search history item
    *
    * @param string $query
    * @return Model|SearchHistory|Builder|null
    */
   public static function addSearch(string $query): Model|SearchHistory|Builder|null
   {
      try {
         /** @var User $user */
         $user = auth()->user();

         /** @var SearchHistory $queryExist */
         $queryExist = self::queryExistAlready($query);

         if ($queryExist !== null) {
            $queryExist->increaseQueryOccurrences();
         } else {
            $queryExist = self::store([
               "query" => $query,
               "number_of_occurrences" => config("torryme.constants.default_increment_or_decrement"),
               "user_id" => $user->{"id"},
            ]);
         }

         $results = $queryExist;
      } catch (\Exception $exception) {
         log_debug(exception: $exception, prefix: 'SearchHistoryService::addSearch');
         $results = null;
      }

      return $results;
   }

   /**
    * Check if query already exist
    *
    * @param string $query
    * @return Model|Builder|null
    */
   public static function queryExistAlready(string $query): Model|Builder|null
   {
      return SearchHistory::query()->where("query", $query)->first();
   }

   /**
    * Search users
    *
    * @param string $query
    * @param int|null $pageNumber
    * @return array
    */
   public static function searchUsers(string $query, int $pageNumber = null): array
   {
      /** @var User $user */
      $user = auth()->user();

      //Search for unauthenticated users
      if ($user == null) {
         $queryBuilder = Search::add(
            User::query()->with('userDetail'), ['user_name', 'userDetail.first_name', 'userDetail.last_name']
         )
            ->paginate(perPage: config("torryme.constants.items_per_page"), pageName: 'page', page: $pageNumber)
            ->beginWithWildcard()
            ->search($query);

         return self::computeUnauthenticatedUserSearchResponse($queryBuilder);
      }


      //Search for authenticated users
      $userId = $user->{"id"};

      $queryBuilder =
         Search::add(
            Follower::query()->with('follower')->where('following_user_id', $user->{'id'}),
            ['follower.user_name', 'follower.userDetail.first_name', 'follower.userDetail.last_name']
         )
            ->add(
               Follower::query()->with('following')->where('follower_user_id', $user->{'id'}),
               ['following.user_name', 'following.userDetail.first_name', 'following.userDetail.last_name']
            )
            ->add(
               User::query()
                  ->with('userDetail')
                  ->whereDoesntHave('userFollower', function (Builder $query) use ($userId) {
                     $query->where('following_user_id', $userId);
                  })->whereDoesntHave('userFollowing', function (Builder $query) use ($userId) {
                     $query->where('follower_user_id', $userId);
                  }),
               ['user_name', 'userDetail.first_name', 'userDetail.last_name']
            )
            ->includeModelType()
            ->orderByModel([Follower::class, User::class])
            ->paginate(perPage: config("torryme.constants.items_per_page"), pageName: 'page', page: $pageNumber)
            ->beginWithWildcard()
            ->search($query);

      $userResultsArray = array();
      foreach ($queryBuilder as $result) {
         if ($result->{"type"} == "Follower") {
            if ($result->{"follower_user_id"} == $user->{"id"}) {
               $result = $result->{"following"};
            } else {
               $result = $result->{"follower"};
            }
         } else {
            $result = $result->load('userDetail');
         }

         $userResultsArray[] = $result;
      }

      //Remove all array duplicates
      $userWithOutDuplicates = array_unique($userResultsArray);

      $simpleUserArray = array();
      foreach ($userWithOutDuplicates as $simpleUser) {
         $simpleUserArray[] = $simpleUser->{"id"};
      }

      $searchedUsers =
         User::query()
            ->whereIn("id", $simpleUserArray)
            ->paginate(
               perPage: config("torryme.constants.items_per_page"),
               page: $pageNumber
            );

      $searchedUsersResults = array();
      foreach ($searchedUsers as $user) {
         $searchedUsersResults[] = $user->load(['userDetail', 'business.businessCategory']);
      }

      return default_paginator_format(
         $searchedUsers->lastPage(),
         $searchedUsers->total(),
         $searchedUsers->currentPage(),
         "data",
         $searchedUsersResults,
      );
   }

   /**
    * Search products
    *
    * @param string $query
    * @param int|null $pageNumber
    * @return array
    */
   public static function searchProducts(string $query, int $pageNumber = null): array
   {
      /** @var User $user */
      $user = auth()->user();

      $businessServiceCategory = BusinessCategory::getServiceBusinessCategories();

      // Search for unauthenticated users
      if ($user == null) {
         $queryBuilder =
            Search::add(Product::query()->whereNot('business_category_id', $businessServiceCategory->{"id"}), ['designation'])
               ->paginate(perPage: config("torryme.constants.items_per_page"), pageName: 'page', page: $pageNumber)
               ->beginWithWildcard()
               ->search($query);

         return self::computeUnauthenticatedUserSearchResponse($queryBuilder);
      }

      // Search for authenticated users
      $queryBuilder =
         Search::add(
            Follower::query()
               ->with('follower')
               ->where('following_user_id', $user->{'id'})
               ->whereHas('follower.business.businessProduct', function (Builder $builder) use ($businessServiceCategory) {
                  $builder->whereNot('business_category_id', $businessServiceCategory->{"id"});
               }), ['follower.business.businessProduct.designation']
         )
            ->add(
               Follower::query()
                  ->with('following')
                  ->where('follower_user_id', $user->{'id'})
                  ->whereHas('following.business.businessProduct', function (Builder $builder) use ($businessServiceCategory) {
                     $builder->whereNot('business_category_id', $businessServiceCategory->{"id"});
                  }), ['following.business.businessProduct.designation']
            )
            ->add(
               Product::query()
                  ->whereNot('business_category_id', $businessServiceCategory->{"id"}), ['designation']
            )
            ->includeModelType()
            ->orderByModel([Follower::class, Product::class])
            ->paginate(perPage: config("torryme.constants.items_per_page"), pageName: 'page', page: $pageNumber)
            ->beginWithWildcard()
            ->search($query);

      return self::computeProductAndServiceSearch($queryBuilder, $user, $pageNumber);
   }

   /**
    * Search services
    *
    * @param string $query
    * @param int|null $pageNumber
    * @return array
    */
   public static function searchServices(string $query, int $pageNumber = null): array
   {
      /** @var User $user */
      $user = auth()->user();
      $businessServiceCategory = BusinessCategory::getServiceBusinessCategories();

      //Search for unauthenticated users
      if ($user == null) {
         $queryBuilder =
            Search::add(
               Product::query()
                  ->where('business_category_id', $businessServiceCategory->{"id"}), ['designation']
            )
               ->paginate(perPage: config("torryme.constants.items_per_page"), pageName: 'page', page: $pageNumber)
               ->beginWithWildcard()
               ->search($query);

         return self::computeUnauthenticatedUserSearchResponse($queryBuilder);
      }


      //Search for authenticated users
      $queryBuilder =
         Search::add(
            Follower::query()
               ->with('follower')
               ->where('following_user_id', $user->{'id'})
               ->whereHas('follower.business.businessProduct', function (Builder $builder) use ($businessServiceCategory) {
                  $builder->where('business_category_id', $businessServiceCategory->{"id"});
               }), ['follower.business.businessProduct.designation']
         )
            ->add(
               Follower::query()
                  ->with('following')
                  ->where('follower_user_id', $user->{'id'})
                  ->whereHas('following.business.businessProduct', function (Builder $builder) use ($businessServiceCategory) {
                     $builder->where('business_category_id', $businessServiceCategory->{"id"});
                  }), ['following.business.businessProduct.designation']
            )
            ->add(
               Product::query()
                  ->where('business_category_id', $businessServiceCategory->{"id"}
                  ),
               ['designation']
            )
            ->includeModelType()
            ->paginate(perPage: config("torryme.constants.items_per_page"), pageName: 'page', page: $pageNumber)
            ->beginWithWildcard()
            ->dontParseTerm()
            ->search($query);

      return self::computeProductAndServiceSearch($queryBuilder, $user, $pageNumber);
   }

   /**
    * Product and service results
    *
    * @param LengthAwarePaginator $queryBuilder
    * @param User $user
    * @param int|null $pageNumber
    * @return array
    */
   public static function computeProductAndServiceSearch(LengthAwarePaginator $queryBuilder, User $user, int $pageNumber = null): array
   {
      $followingProductsArray = array();
      $followerProductsArray = array();
      $productSearchResultsArray = array();

      foreach ($queryBuilder as $product) {
         if ($product->{"type"} == "Follower") {
            if ($product->{"follower_user_id"} == $user->{"id"}) {
               $followingProducts = $product->{"following"}->{"business"}->{'businessProduct'};
               foreach ($followingProducts as $object) {
                  $followingProductsArray[] = $object;
               }
            } else {
               $followerProducts = $product->{"follower"}->{"business"}->{'businessProduct'};
               foreach ($followerProducts as $object) {
                  $followerProductsArray[] = $object;
               }
            }
         } else {
            $productSearchResultsArray[] = $product;
         }
      }

      // Merge follower and following search results
      $mergeFollowerFollowingProducts = array_merge($followingProductsArray, $followerProductsArray);

      // Merge mergeFollowerFollowingProducts with product table search results
      $mergeAllProductResults = array_merge(array_unique($mergeFollowerFollowingProducts), $productSearchResultsArray);

      // Get product Ids from mergeAllProductResults array
      $productIdArray = array_column($mergeAllProductResults, 'id');

      // Get unique product ids from productIdArray array
      $uniqueProductIds = array_unique($productIdArray);

      $searchedProducts =
         Product::query()
            ->whereIn("id", $uniqueProductIds)
            ->paginate(
               perPage: config("torryme.constants.items_per_page"),
               page: $pageNumber
            );

      $searchedProductsResults = array();
      foreach ($searchedProducts as $product) {
         $searchedProductsResults[] = $product->computeDetailsForVisitor();
      }

      return default_paginator_format(
         $searchedProducts->lastPage(),
         $searchedProducts->total(),
         $searchedProducts->currentPage(),
         "data",
         $searchedProductsResults,
      );
   }

   /**
    * Compute search response for unauthenticated users
    *
    * @param LengthAwarePaginator $queryBuilder
    * @return array
    */
   public static function computeUnauthenticatedUserSearchResponse(LengthAwarePaginator $queryBuilder): array
   {
      $searchResults = array();
      foreach ($queryBuilder as $search) {
         $searchResults[] = $search;
      }

      return default_paginator_format(
         $queryBuilder->lastPage(),
         $queryBuilder->total(),
         $queryBuilder->currentPage(),
         "data",
         $searchResults,
      );
   }

   /**
    * Hash tags search
    *
    * @param string $query
    * @param int|null $pageNumber
    * @return array
    */
   public static function searchHashtags(string $query, int $pageNumber = null): array
   {
      $queryBuilder =
         Search::add(
            HashTag::class,
            ['designation']
         )
            ->paginate(perPage: config("torryme.constants.items_per_page"), pageName: 'page', page: $pageNumber)
            ->beginWithWildcard()
            ->dontParseTerm()
            ->search($query);

      $results = array();
      foreach ($queryBuilder as $hashTag) {

         if ($hashTag->productHashTag()->exists()) {
            $results[] = array(
               "created_at" => $hashTag->{"created_at"},
               "hash_tag" => $hashTag->{"designation"},
               "total_product" => $hashTag->productHashTag()->count(),
            );
         }

         if ($hashTag->postHashTag()->exists()) {
            $results[] = array(
               "created_at" => $hashTag->{"created_at"},
               "hash_tag" => $hashTag->{"designation"},
               "total_post" => $hashTag->postHashTag()->count(),
            );
         }

         if ($hashTag->productHashTag()->exists() && $hashTag->postHashTag()->exists()) {
            $results[] = array(
               "created_at" => $hashTag->{"created_at"},
               "hash_tag" => $hashTag->{"designation"},
               "total_product" => $hashTag->productHashTag()->count(),
               "total_post" => $hashTag->postHashTag()->count(),
            );
         }

      }

      return default_paginator_format(
         $queryBuilder->lastPage(),
         $queryBuilder->total(),
         $queryBuilder->currentPage(),
         "data",
         $results,
      );
   }

   /**
    * Live search
    *
    * @return void
    */
   public static function liveSearch()
   {
   }

   /**
    * Compute top tag for search
    *
    * @param string $query
    * @param $users
    * @param $products
    * @param $services
    * @param $lives
    * @param $hashTags
    * @return array
    */
   public static function computeTopSearch(string $query, $users, $products, $services, $lives, $hashTags): array
   {
      $result = [];

      foreach ($users as $user) {
         $result[] = $user->{'user_name'};
         $result[] = $user->fullName();
         $result[] = $user->{'business'}?->{'designation'};
      }

      foreach ($products as $product) {
         $result[] = $product['designation'];
      }

      foreach ($services as $service) {
         $result[] = $service['designation'];
      }

      foreach ($hashTags as $hashTag) {
         $result[] = $hashTag['hash_tag'];
      }

      return array_unique($result);
   }
}
