<?php

namespace XF\Like;

use XF\Entity\LikedContent;
use XF\Entity\User;
use XF\Mvc\Entity\Entity;
use XF\Repository\NewsFeedRepository;
use XF\Repository\UserAlertRepository;

use function is_array;

abstract class AbstractHandler
{
	protected $contentType;

	protected $contentCacheFields = [
		'count' => 'likes',
		'recent' => 'like_users',
	];

	public function __construct($contentType)
	{
		$this->contentType = $contentType;
	}

	abstract public function likesCounted(Entity $entity);

	public function canViewContent(Entity $entity, &$error = null)
	{
		if (method_exists($entity, 'canView'))
		{
			return $entity->canView($error);
		}

		throw new \LogicException("Could not determine content viewability; please override");
	}

	public function getTemplateName()
	{
		return 'public:like_item_' . $this->contentType;
	}

	public function getTemplateData(LikedContent $like, ?Entity $content = null)
	{
		if (!$content)
		{
			$content = $like->Content;
		}

		return [
			'like' => $like,
			'content' => $content,
		];
	}

	public function render(LikedContent $like, ?Entity $content = null)
	{
		if (!$content)
		{
			$content = $like->Content;
			if (!$content)
			{
				return '';
			}
		}

		$template = $this->getTemplateName();
		$data = $this->getTemplateData($like, $content);

		return \XF::app()->templater()->renderTemplate($template, $data);
	}

	public function isRenderable(LikedContent $like)
	{
		return $this->isLikeRenderable($like);
	}

	public function isLikeRenderable(LikedContent $like)
	{
		$template = $this->getTemplateName();
		return \XF::app()->templater()->isKnownTemplate($template);
	}

	public function updateContentLikes(Entity $entity, $count, array $latestLikes)
	{
		if (is_array($count))
		{
			$count = reset($count);
		}

		$countField = $this->contentCacheFields['count'] ?? false;
		$recentField = $this->contentCacheFields['recent'] ?? false;

		if (!$countField && !$recentField)
		{
			return;
		}

		if ($countField)
		{
			$entity->$countField = $count;
		}
		if ($recentField)
		{
			$entity->$recentField = $latestLikes;
		}

		$entity->save();
	}

	public function updateRecentCacheForUserChange($oldUserId, $newUserId, $oldUsername, $newUsername)
	{
		if (empty($this->contentCacheFields['recent']))
		{
			return;
		}

		$entityType = \XF::app()->getContentTypeEntity($this->contentType, false);
		if (!$entityType)
		{
			return;
		}

		$structure = \XF::em()->getEntityStructure($entityType);

		// note that xf_reaction_content must already be updated
		$oldFind = $this->getUserStringForLikeUsers($oldUserId, $oldUsername);
		$newReplace = $this->getUserStringForLikeUsers($newUserId, $newUsername);

		$recentField = $this->contentCacheFields['recent'];
		$table = $structure->table;
		$primaryKey = $structure->primaryKey;

		\XF::db()->query("
			UPDATE (
				SELECT content_id FROM xf_reaction_content
				WHERE content_type = ?
				AND reaction_user_id = ?
			) AS temp
			INNER JOIN {$table} AS reaction_table ON (reaction_table.`$primaryKey` = temp.content_id)
			SET reaction_table.`{$recentField}` = REPLACE(reaction_table.`{$recentField}`, ?, ?)
			WHERE reaction_table.`{$recentField}` LIKE ?
		", [$this->contentType, $newUserId, $oldFind, $newReplace, '%' . \XF::db()->escapeLike($oldFind) . '%']);
	}

	protected function getUserStringForLikeUsers($userId, $username)
	{
		return substr(json_encode(['user_id' => $userId, 'username' => $username]), 1, -1);
	}

	public function getContentLikeCaches(Entity $entity)
	{
		$countField = $this->contentCacheFields['count'] ?? false;
		$recentField = $this->contentCacheFields['recent'] ?? false;
		$output = [];

		if ($countField)
		{
			$output['count'] = $entity->$countField;
		}
		if ($recentField)
		{
			$output['recent'] = $entity->$recentField;
		}

		return $output;
	}

	public function sendLikeAlert(User $receiver, User $sender, $contentId, Entity $content)
	{
		$canView = \XF::asVisitor($receiver, function () use ($content)
		{
			return $this->canViewContent($content);
		});
		if (!$canView)
		{
			return false;
		}

		/** @var UserAlertRepository $alertRepo */
		$alertRepo = \XF::repository(UserAlertRepository::class);
		return $alertRepo->alertFromUser(
			$receiver,
			$sender,
			$this->contentType,
			$contentId,
			'like',
			$this->getExtraDataForAlertOrFeed($content, 'alert')
		);
	}

	public function removeLikeAlert(LikedContent $like)
	{
		/** @var UserAlertRepository $alertRepo */
		$alertRepo = \XF::repository(UserAlertRepository::class);
		$alertRepo->fastDeleteAlertsFromUser($like->reaction_user_id, $this->contentType, $like->content_id, 'like');
	}

	public function publishLikeNewsFeed(User $sender, $contentId, Entity $content)
	{
		/** @var NewsFeedRepository $newsFeedRepo */
		$newsFeedRepo = \XF::repository(NewsFeedRepository::class);
		$newsFeedRepo->publish(
			$this->contentType,
			$contentId,
			'like',
			$sender->user_id,
			$sender->username,
			$this->getExtraDataForAlertOrFeed($content, 'feed')
		);
	}

	public function unpublishLikeNewsFeed(LikedContent $like)
	{
		/** @var NewsFeedRepository $newsFeedRepo */
		$newsFeedRepo = \XF::repository(NewsFeedRepository::class);
		$newsFeedRepo->unpublish($this->contentType, $like->content_id, $like->reaction_user_id, 'like');
	}

	protected function getExtraDataForAlertOrFeed(Entity $content, $context)
	{
		return [];
	}

	public function getContentUserId(Entity $entity)
	{
		if (isset($entity->user_id))
		{
			return $entity->user_id;
		}
		else if (isset($entity->User))
		{
			$user = $entity->User;
			if ($user instanceof User)
			{
				return $user->user_id;
			}
			else
			{
				throw new \LogicException("Found a User relation but it did not match a user; please override");
			}
		}

		throw new \LogicException("Could not determine content user ID; please override");
	}

	public function getEntityWith()
	{
		return [];
	}

	public function getContent($id)
	{
		return \XF::app()->findByContentType($this->contentType, $id, $this->getEntityWith());
	}

	public function getContentType()
	{
		return $this->contentType;
	}
}
