<?php

namespace App\Services;

use App\Enums\AccountPrivacyEnum;
use App\Enums\GenericStatusEnum;
use App\Enums\PostTimelineEnum;
use App\Models\HashTag;
use App\Models\Like;
use App\Models\Post;
use App\Models\PostHashTag;
use App\Models\PostMention;
use App\Models\Product;
use App\Models\ProductRating;
use App\Models\Save;
use App\Models\Share;
use App\Models\ShareRecipient;
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 Illuminate\Support\Facades\DB;

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

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

   /**
    * Add post likes
    *
    * @return mixed
    */
   public function addPostLike(): mixed
   {
      $newNumOfLikes = $this->{"likes"} + config("torryme.constants.default_increment_or_decrement");
      $this->updateService([
         'likes' => $newNumOfLikes
      ]);

      return $newNumOfLikes;
   }

   /**
    * Subtract post likes
    *
    * @return mixed
    */
   public function subtractPostLike(): mixed
   {
      $currentNumOfLikes = $this->{"likes"};

      if ($currentNumOfLikes > config("torryme.constants.default_zero_number")) {
         $newNumOfLikes = $currentNumOfLikes - config("torryme.constants.default_increment_or_decrement");

         $currentNumOfLikes = $this->updateService([
            'likes' => $newNumOfLikes
         ]);
      }

      return $currentNumOfLikes;
   }

   /**
    * Add number of post saves
    *
    * @return mixed
    */
   public function addPostSave(): mixed
   {
      $newNumOfSaves = $this->{"saves"} + config("torryme.constants.default_increment_or_decrement");
      $this->updateService(['saves' => $newNumOfSaves]);

      return $newNumOfSaves;
   }

   /**
    * Subtract number of post saves
    *
    * @return mixed
    */
   public function subtractPostSave(): mixed
   {
      $currentNunOfSaves = $this->{"saves"};

      if ($currentNunOfSaves > config("torryme.constants.default_zero_number")) {
         $newNumOfSaves = $currentNunOfSaves - config("torryme.constants.default_increment_or_decrement");
         $currentNunOfSaves = $this->updateService(['saves' => $newNumOfSaves]);
      }

      return $currentNunOfSaves;
   }

   /**
    * Add number of post comments
    *
    * @return mixed
    */
   public function addNumberOfPostComment(): mixed
   {
      $newNumOfComments = $this->{"comments"} + config("torryme.constants.default_increment_or_decrement");
      $this->updateService(['comments' => $newNumOfComments]);

      return $newNumOfComments;
   }

   /**
    * Subtract post comment
    *
    * @return mixed
    */
   public function subtractNumberOfPostComment(): mixed
   {
      $currentNunOfComments = $this->{"comments"};

      if ($currentNunOfComments > config("torryme.constants.default_zero_number")) {
         $newNumOfComments = $currentNunOfComments - config("torryme.constants.default_increment_or_decrement");
         $currentNunOfComments = $this->updateService(['comments' => $newNumOfComments]);
      }

      return $currentNunOfComments;
   }

   /**
    * Add post share
    *
    * @return mixed
    */
   public function addPostShare(): mixed
   {
      $newNumOfShares = $this->{"shares"} + config("torryme.constants.default_increment_or_decrement");
      $this->updateService(['shares' => $newNumOfShares]);

      return $newNumOfShares;
   }

   /**
    * Subtract post share
    *
    * @return mixed
    */
   public function subtractPostShare(): mixed
   {
      $currentNunOfShares = $this->{"shares"};

      if ($currentNunOfShares > config("torryme.constants.default_zero_number")) {
         $newNumOfComments = $currentNunOfShares - config("torryme.constants.default_increment_or_decrement");
         $currentNunOfShares = $this->updateService(['shares' => $newNumOfComments]);
      }

      return $currentNunOfShares;
   }

   /**
    * Add extra attributes
    *
    * @param User|null $user
    * @return Post
    */
   public function addExtraAttributes(User $user = null): Post
   {
      $post = $this;
      $authUser = $user ?? auth()->user();

      // Other attributes ...
      $alreadyShared = $authUser !== null && Share::alreadySharedPost($authUser, $post->{"id"});
      $alreadyLiked = $authUser !== null && Like::alreadyLikedPost($authUser, $post->{"id"});
      $lastUserRating = $authUser !== null && $post->{"product_id"} != null ? ProductRating::lastUserRating($authUser, $post->{"product_id"}) : null;
      $canComment = $post->whoCanCommentPost();
      $canDownload = $post->whoCanDownloadPost();

      $post['already_shared'] = $alreadyShared ? config("torryme.constants.default_one_number") : config("torryme.constants.default_zero_number");
      $post['total_saved'] = Save::totalSavePost($authUser, $post->{"id"});
      $post['already_liked'] = $alreadyLiked ? config("torryme.constants.default_one_number") : config("torryme.constants.default_zero_number");
      $post['user_rating'] = $lastUserRating;
      $post['can_rate_product'] = $post->{"product"} !== null && $authUser->canRateProduct($post->{"product_id"}) ? config("torryme.constants.default_one_number") : config("torryme.constants.default_zero_number");
      $post['can_comment'] = $canComment ? config("torryme.constants.default_one_number") : config("torryme.constants.default_zero_number");
      $post['can_download'] = $canDownload ? config("torryme.constants.default_one_number") : config("torryme.constants.default_zero_number");

      return $post;
   }

   /**
    * Who can comment
    *
    * @return bool
    */
   public function whoCanCommentPost(): bool
   {
      $post = $this;
      $canComment = true;;

      /** @var User $authUser */
      $authUser = auth()->user();
      // Get owner of the post inorder to get account privacy setting
      $postOwner = $post->owner();

      // Get post owner account privacy setting
      $postOwnerAccountPrivacySetting = $postOwner->{"accountPrivacySetting"};

      if ($postOwnerAccountPrivacySetting->{"enable_privacy"} == GenericStatusEnum::enable->value) {
         if ($postOwnerAccountPrivacySetting->{"who_can_comment"} == AccountPrivacyEnum::everyOne->value || $postOwnerAccountPrivacySetting->{"who_can_comment"} == AccountPrivacyEnum::phoneContacts->value) {
            $canComment = true;
         } elseif ($postOwnerAccountPrivacySetting->{"who_can_comment"} == AccountPrivacyEnum::noOne->value) {
            $canComment = false;
         } elseif ($postOwnerAccountPrivacySetting->{"who_can_comment"} == AccountPrivacyEnum::followers->value) {
            $canComment = $authUser->isFollower($postOwner->{"id"});
         }
      }

      return $canComment;
   }

   /**
    * Who can download post
    *
    * @return bool
    */
   public function whoCanDownloadPost(): bool
   {
      $post = $this;

      /** @var User $authUser */
      $authUser = auth()->user();
      // Get owner of the post inorder to get account privacy setting
      $postOwner = $post->owner();

      $canDownloadPost = true;

      //Get post owner account privacy setting
      $postOwnerAccountPrivacySetting = $postOwner->{"accountPrivacySetting"};

      if ($postOwnerAccountPrivacySetting->{"enable_privacy"} == GenericStatusEnum::enable->value) {
         if ($postOwnerAccountPrivacySetting->{"who_can_download_post"} == AccountPrivacyEnum::everyOne->value || $postOwnerAccountPrivacySetting->{"who_can_download_post"} == AccountPrivacyEnum::phoneContacts->value) {
            $canDownloadPost = true;
         } elseif ($postOwnerAccountPrivacySetting->{"who_can_download_post"} == AccountPrivacyEnum::noOne->value) {
            $canDownloadPost = false;
         } elseif ($postOwnerAccountPrivacySetting->{"who_can_download_post"} == AccountPrivacyEnum::followers->value) {
            $canDownloadPost = $authUser->isFollower($postOwner->{"id"});
         }
      }

      return $canDownloadPost;
   }

   /**
    * Get post owner
    *
    * @return mixed
    */
   public function owner(): mixed
   {
      return $this->{'user'} ?? $this->{'business'};
   }

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

   /**
    * Total product post
    *
    * @param Product $product
    * @return int
    */
   public static function totalProductPost(Product $product): int
   {
      return
         Post::query()
            ->where('product_id', $product->{'id'})
            ->whereNotNull('completed_at')
            ->count();
   }

   /**
    * Create new post
    *
    * @param array $data
    * @return Model|Builder|null
    * @throws \Exception
    */
   public static function createPost(array $data): Model|Builder|null
   {
      DB::beginTransaction();

      try {
         /** @var User $user */
         $user = auth()->user();
         $business = $user->{'business'};

         // Create post
         $communityId = $data['community_id'] ?? null;

         /** @var Post $post */
         $post = Post::store(array(
            'content' => $data['content'] ?? "",
            'business_id' => $business?->{'id'},
            'user_id' => $business === null ? $user->{'id'} : null,

            'product_id' => $data['product_id'] ?? null,
            'community_id' => $communityId,
            'timeline_type' => $communityId === null ? PostTimelineEnum::feedTimeline->value : PostTimelineEnum::communityTimeline->value,
         ));

         if (isset($data['product_id'])) {
            /** @var Product $product */
            $product = Product::findById($data['product_id']);

            $product?->updateService(array(
               'published_at' => now()
            ));
         }

         // Create post hashtags
         self::handlePostHashTags($data, $post);

         // Add post mention
         self::handlePostMentions($data, $post);

         if (isset($data['product_id'])) {
            // Update post ...
            $post->updateService([
               'completed_at' => now()
            ]);
         }

         DB::commit();

         $result = $post->refresh();
      } catch (\Exception $exception) {
         log_debug($exception, "PostService::createPost");
         DB::rollBack();

         throw $exception;
      }

      return $result;
   }

   /**
    * Republish a post
    *
    * @return Post
    * @throws \Exception
    */
   public function republish(): Post
   {
      DB::beginTransaction();

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

         // Create post
         /** @var Post $post */
         $post = Post::store(array(
            'content' => "",
            'business_id' => $user->{'business'}?->{'id'},
            'user_id' => $user->{'business'} === null ? $user->{'id'} : null,
            'reposted_post_id' => $this->{'id'},
            'timeline_type' => PostTimelineEnum::feedTimeline->value,
            'completed_at' => now(),
         ));

         $this->updateService([
            'shares' => $this->{'shares'} + 1,
         ]);

         DB::commit();
         $result = $post->refresh();
      } catch (\Exception $exception) {
         log_debug($exception, "PostService::republish");
         DB::rollBack();

         throw $exception;
      }

      return $result;
   }

   /**
    * Republish community post
    *
    * @return Post
    * @throws \Exception
    */
   public function republishCommunityPost(): Post
   {
      DB::beginTransaction();

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

         // Create post
         /** @var Post $post */
         $post = Post::store(array(
            'content' => "",
            'business_id' => $user->{'business'}?->{'id'},
            'user_id' => $user->{'business'} === null ? $user->{'id'} : null,
            'community_id' => $this->{'community_id'},
            'reposted_post_id' => $this->{'id'},
            'timeline_type' => PostTimelineEnum::communityTimeline->value,
            'completed_at' => now(),
         ));

         $this->updateService([
            'shares' => $this->{'shares'} + 1,
         ]);

         DB::commit();
         $result = $post->refresh();
      } catch (\Exception $exception) {
         log_debug($exception, "PostService::republishCommunityPost");
         DB::rollBack();

         throw $exception;
      }

      return $result;
   }

   /**
    * Handle post hashtags
    *
    * @param array $data
    * @param Post $post
    */
   public static function handlePostHashTags(array $data, Post $post): void
   {
      $hasTags = $data['hash_tags'] ?? [];
      foreach ($hasTags as $tag) {
         $hashTag = HashTag::checkExist($tag);

         if ($hashTag === null) {
            $hashTag = HashTag::store([
               "designation" => $tag,
            ]);
         }

         PostHashTag::store([
            'post_id' => $post->{'id'},
            'hash_tag_id' => $hashTag->{'id'},
         ]);
      }
   }

   /**
    * Handle post mentions
    *
    * @param array $data
    * @param Post $post
    */
   public static function handlePostMentions(array $data, Post $post): void
   {
      $mentions = $data['mentions'] ?? [];
      foreach ($mentions as $mention) {
         $id = (int)$mention;

         if ($id > config("torryme.constants.default_zero_number") && User::findById($id) !== null) {
            PostMention::store(array(
               "mentioned_user_id" => $id,
               "post_id" => $post->{'id'},
            ));
         }
      }
   }

   /**
    * Get business/User's posts
    *
    * @param User $user
    * @param int|null $pageNumber
    * @return array
    */
   public static function getUsersPost(User $user, int $pageNumber = null): array
   {
      $business = $user->{'business'};

      if ($business != null) {
         $queryBuilder =
            Post::query()
               ->with(['postAttachments', 'product'])
               ->orWhere("business_id", $business->{"id"})
               ->orWhere("user_id", $business->{"owner_user_id"})
               ->whereNotNull('completed_at')
               ->orderByDesc('created_at');
      } else {
         $queryBuilder =
            Post::query()
               ->with(['postAttachments', 'product'])
               ->where("user_id", $user->{"id"})
               ->whereNotNull('completed_at')
               ->orderByDesc('created_at');
      }

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

      return Post::buildPostsPaginator($paginator);
   }

   /**
    * Get psst hashtags
    *
    * @return array
    */
   public function computePostHashTags(): array
   {
      return
         $this->postHags()
            ->with('hashTag')
            ->get()
            ->pluck('hashTag.designation')
            ->toArray();
   }

   /**
    * Get post mentions
    *
    * @return Collection
    */
   public function computePostMentions(): Collection
   {
      return $this->postMentions()->with('mentionedUser.userDetail')->get();
   }

   /**
    * Get post feeds
    *
    * @param int|null $pageNumber
    * @param bool $onlyBusinessPost
    * @return LengthAwarePaginator
    */
   public static function getPostFeeds(int $pageNumber = null, bool $onlyBusinessPost = false): LengthAwarePaginator
   {
      $queryBuilder =
         Post::query()
            ->where("timeline_type", PostTimelineEnum::feedTimeline->value)
            ->whereNotNull('completed_at');

      if ($onlyBusinessPost) {
         $queryBuilder = $queryBuilder->whereHas('product');
      }

      $queryBuilder = $queryBuilder->orderByDesc('created_at');

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

   /**
    * Get community post feeds
    *
    * @param int $communityId
    * @param int|null $pageNumber
    * @return LengthAwarePaginator
    */
   public static function getCommunityPostFeeds(int $communityId, int $pageNumber = null): LengthAwarePaginator
   {
      $queryBuilder =
         Post::query()
            ->where('community_id', $communityId)
            ->where('timeline_type', PostTimelineEnum::communityTimeline->value)
            ->whereNotNull('completed_at')
            ->orderByDesc('created_at');

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

   /**
    * Create repost
    *
    * @param array $data
    * @return Model|Builder|null
    */
   public function shareToAnother(array $data): Model|Builder|null
   {
      DB::beginTransaction();

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

         // Create share
         $share = Share::store([
            "remark" => $data["remark"],
            "post_id" => $this->{"id"},
            "user_id" => $user->{"id"},
         ]);

         if (isset($data["user_ids"])) {
            foreach ($data["user_ids"] as $key => $value) {
               ShareRecipient::store([
                  "share_id" => $share->{"id"},
                  "user_recipient_id" => $value,
               ]);
            }
         }

         // Add post share
         $this->addPostShare();

         DB::commit();
         $result = $this->refresh();
      } catch (\Exception $exception) {
         log_debug($exception, "PostService::shareToAnother");
         DB::rollBack();
         $result = null;
      }

      return $result;
   }

   /**
    * Build posts paginator
    *
    * @param LengthAwarePaginator $paginator
    * @return array
    */
   public static function buildPostsPaginator(LengthAwarePaginator $paginator): array
   {
      $postsResultArray = array();

      foreach ($paginator->items() as $post) {
         $postsResultArray[] = $post->commonSerialization();
      }

      return default_paginator_format(
         $paginator->lastPage(),
         $paginator->total(),
         $paginator->currentPage(),
         'posts',
         $postsResultArray,
      );
   }

   /**
    * Get total number of user's post likes
    *
    * @param int $userId
    * @return mixed
    */
   public static function getTotalUserPostLikes(int $userId): mixed
   {
      return
         Post::query()
            ->where("user_id", $userId)
            ->whereNotNull('completed_at')
            ->sum("likes");
   }

   /**
    * Get the total number of user's post saves
    *
    * @param int $userId
    * @return mixed
    */
   public static function getTotalUserPostSaves(int $userId): mixed
   {
      return
         Post::query()
            ->where("user_id", $userId)
            ->whereNotNull('completed_at')
            ->sum("saves");
   }

   /**
    * Get the total number of user's post shares
    * @param int $userId
    * @return mixed
    */
   public static function getTotalUserPostShares(int $userId): mixed
   {
      return
         Post::query()
            ->where("user_id", $userId)
            ->whereNotNull('completed_at')
            ->sum("shares");
   }

   /**
    * Get user's most recent post
    *
    * @param User $user
    * @param int|null $pageNumber
    * @return array
    */
   public static function userMostRecentPost(User $user, int $pageNumber = null): array
   {
      $business = $user->{'business'};

      if ($business != null) {
         $queryBuilder =
            Post::query()
               ->with(['postAttachments', 'product'])
               ->orWhere("business_id", $business->{"id"})
               ->orWhere("user_id", $business->{"owner_user_id"})
               ->whereNotNull('completed_at')
               ->orderByDesc("created_at");
      } else {
         $queryBuilder =
            Post::query()
               ->with(['postAttachments', 'product'])
               ->where("user_id", $user->{"id"})
               ->whereNotNull('completed_at')
               ->orderByDesc("created_at")
               ->limit(50);
      }

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

      return Post::buildPostsPaginator($postPaginator);
   }

   /**
    * Compute common post details
    *
    * @param User|null $authUser
    * @return Post
    */
   public function commonSerialization(User $authUser = null): Post
   {
      $post = $this;

      /** @var Product $product */
      $product = $post->{"product"};
      $productDetails = $product?->computeDetailsForPost();

      $user = $post->owner();

      // Remove unused relations ...
      $post->unsetRelation('product');
      $post->unsetRelation('user');
      $post->unsetRelation('reposted_post');
      $user->unsetRelation('accountPrivacySetting');

      $post->load('postAttachments');
      $post['hash_tags'] = $post->computePostHashTags();
      $post['mentions'] = $post->computePostMentions();
      $post['product'] = $productDetails;
      $post['reposted_post'] = $post->{"repostedPost"}?->commonSerialization();;
      $post['user'] = $user->load(['userDetail', 'business.businessCategory']);
      $post = $post->addExtraAttributes($authUser);

      // ...
      unset($post['account_privacy_setting']);

      return $post;
   }

   /**
    * Turn on comment
    *
    * @return Post
    */
   public function turnOnComment(): Post
   {
      $post = $this;
      if ($post->{"turn_on_comment"} == GenericStatusEnum::enable->value) {
         $post->updateService([
            "turn_on_comment" => GenericStatusEnum::disable->value
         ]);
      } else {
         $post->updateService([
            "turn_on_comment" => GenericStatusEnum::enable->value
         ]);
      }

      return $post;
   }

   /**
    * Delete post
    *
    * @return void
    */
   public function deletePOst(): void
   {
      $post = $this;
      DB::beginTransaction();

      try {
         // Delete post attachments
         $post->postAttachments()->delete();
         // TODO Delete attachment files

         // Delete post hashtags
         $post->postHags()->delete();

         // Delete post mentions
         $post->postMentions()->delete();

         // Delete post
         $post->delete();

         DB::commit();
      } catch (\Exception $exception) {
         DB::rollBack();
         log_debug($exception, "PostService::deletePOst");
      }
   }

   /**
    * Check if comments are enabled
    *
    * @return bool
    */
   public function commentTurnOn(): bool
   {
      return $this->{"turn_on_comment"} === GenericStatusEnum::enable->value;
   }

   /**
    * Search for restricted words
    *
    * @param $needles
    * @param $haystack
    * @return int
    */
   public function searchRestrictedWord($needles, $haystack): int
   {
      return count(array_intersect($needles, explode(" ", preg_replace("/[^A-Za-z0-9' -]/", "", $haystack))));
   }
}
