<?php

namespace XF\Service\Feed;

use GuzzleHttp\Client;
use Laminas\Feed\Reader\Feed\Rss;
use Laminas\Feed\Reader\Reader;
use XF\App;
use XF\Html\Renderer\BbCode;
use XF\PrintableException;
use XF\Service\AbstractService;
use XF\Util\Str;
use XF\Validator\Url;

class ReaderService extends AbstractService
{
	protected $url;

	/** @var Client */
	protected $client;

	/** @var Rss|null */
	protected $feed;

	protected $feedData;

	/** @var \Exception */
	protected $exception;

	public function __construct(App $app, $url)
	{
		parent::__construct($app);
		$this->setUrl($url);
		$this->setClient();
		$this->setFeed();
	}

	public function setUrl($url)
	{
		/** @var Url $validator */
		$validator = $this->app->validator(Url::class);
		$validator->coerceValue($url);
		if (!$validator->isValid($url))
		{
			throw new PrintableException(\XF::phrase('please_enter_valid_url'));
		}
		$this->url = $url;
	}

	protected function setClient()
	{
		$this->client = $this->app->http()->client();
	}

	protected function setFeed()
	{
		try
		{
			$content = $this->client->get($this->url)
				->getBody()
				->getContents();

			$this->feed = Reader::importString($content);
		}
		catch (\Exception $e)
		{
			$this->exception = $e;
		}
	}

	public function getFeed()
	{
		return $this->feed;
	}

	public function getTitle()
	{
		$feed = $this->feed;
		if ($feed)
		{
			return $feed->getTitle();
		}

		return '';
	}

	public function getFeedData($withEntries = true)
	{
		$feed = $this->feed;

		if (empty($feed))
		{
			return [];
		}

		$this->feedData = [
			'id' => $feed->getId(),
			'title' => $feed->getTitle(),
			'link' => $feed->getLink(),
			'date_modified' => $feed->getDateModified(),
			'description' => $feed->getDescription(),
			'language' => $feed->getLanguage(),
			'image' => $feed->getImage(),
			'generator' => $feed->getGenerator(),
			'baseUrl' => dirname($this->url),
			'entries' => [],
		];

		if ($withEntries)
		{
			$this->feedData['entries'] = $this->getFeedEntries();
		}

		$this->feedData = \XF::cleanArrayStrings($this->feedData);

		return $this->feedData;
	}

	public function getFeedEntries()
	{
		$entries = [];

		$feed = $this->feed;
		foreach ($feed AS $entry)
		{
			$title = $entry->getTitle();
			if (!$title)
			{
				$title = trim(strip_tags($entry->getDescription()));
			}
			$description = $entry->getDescription() ?: $entry->getTitle();

			try
			{
				$content = $entry->getContent();
			}
			catch (\Exception $e)
			{
				// there's a situation where getting the content can trigger an exception if malformed,
				// so ensure this doesn't error
				$content = $description;
			}

			// if wrong date format is used or date is missing from the entry then the feed
			// is invalid but try to workaround it by getting the feed date or current time
			$dateModified = $entry->getDateModified() ?: $feed->getDateModified();
			if ($dateModified)
			{
				$dateModified = $dateModified->getTimestamp();
			}
			else
			{
				$dateModified = time();
			}

			$entryData = [
				'id' => $entry->getId(),
				'title' => html_entity_decode($title, ENT_COMPAT, 'utf-8'),
				'description' => html_entity_decode($description, ENT_COMPAT, 'utf-8'),
				'date_modified' => $dateModified,
				'author' => $this->getAuthorsAsString($entry->getAuthors()),
				'link' => $entry->getLink(),
				'content' => $this->getContent($content),
			];

			$enclosure = $entry->getEnclosure();
			if ($enclosure)
			{
				$entryData['enclosure_url'] = $enclosure->url;
				$entryData['enclosure_length'] = $enclosure->length;
				$entryData['enclosure_type'] = $enclosure->type;
			}

			if (isset($entryData['id']) && Str::strlen($entryData['id']) > 250)
			{
				$entryData['id'] = md5($entryData['id']);
			}

			$entries[] = $entryData;
		}

		return $entries;
	}

	public function getException()
	{
		return $this->exception;
	}

	protected function getAuthorsAsString($feedAuthors)
	{
		$authorNames = [];

		if ($feedAuthors)
		{
			foreach ($feedAuthors AS $author)
			{
				if (isset($author['name']))
				{
					$authorNames[] = $author['name'];
				}
				else if (isset($author['email']))
				{
					$authorNames[] = $author['email'];
				}
			}
		}

		$authorNames = array_unique($authorNames);

		return implode(', ', $authorNames);
	}

	protected function getContent($html)
	{
		$html = preg_replace('#<p#i', '<br>$0', $html);
		$html = preg_replace('#</p>(?!\s*<br)#i', '$0<br>', $html);

		return BbCode::renderFromHtml($html, [
			'baseUrl' => $this->feedData['baseUrl'],
		]);
	}
}
