<?php

namespace XF\Import\Data;

use XF\Entity\Phrase;
use XF\Entity\Template;
use XF\Import\DataManager;
use XF\Util\Ip;

use function is_array, strval;

abstract class AbstractData
{
	/**
	 * @var DataManager
	 */
	protected $dataManager;

	/**
	 * @var bool
	 */
	protected $log = true;

	protected $checkExisting = true;

	protected $useTransaction = true;

	/**
	 * @var bool
	 */
	protected $allowRetainIds = true;

	/**
	 * This text will be used as content for any required text field that comes through empty
	 *
	 * @var array
	 */
	protected $defaultText = [
		'title' => '[No title]',
		'message' => '[No message]',
		'response' => '[No response text]',
		'username' => 'Guest',
	];

	protected $logExtra = [];

	abstract public function getImportType();
	abstract public function set($field, $value, array $options = []);
	abstract public function get($field);
	abstract protected function write($oldId);
	abstract protected function importedIdFound($oldId, $newId);

	public function __construct(DataManager $dataManager, $log = true)
	{
		$this->dataManager = $dataManager;
		$this->log = $log;

		$this->init();
	}

	protected function init()
	{
	}

	/**
	 * Use this to prevent IDs being retained, even when the import has been configured to retain them.
	 * This is necessary when the source ID format does is not compatible with the destination ID format.
	 */
	public function preventRetainIds()
	{
		$this->allowRetainIds = false;
	}

	public function retainIds()
	{
		return $this->allowRetainIds && $this->dataManager->getRetainIds();
	}

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

	public function checkExisting($check)
	{
		$this->checkExisting = $check;
	}

	public function useTransaction($use)
	{
		$this->useTransaction = $use;
	}

	public function isLogged()
	{
		return $this->log;
	}

	public function bulkSet(array $values, array $options = [])
	{
		foreach ($values AS $key => $value)
		{
			$this->set($key, $value, $options);
		}
	}

	public function save($oldId)
	{
		if ($oldId !== false && $this->log && $this->checkExisting)
		{
			$mappedId = $this->dataManager->lookup($this->getImportType(), $oldId);
			if ($mappedId !== false)
			{
				return $mappedId;
			}
		}

		$preSave = $this->preSave($oldId);
		if ($preSave === false)
		{
			return false;
		}

		$db = $this->dataManager->db();
		if ($this->useTransaction)
		{
			$db->beginTransaction();
		}

		try
		{
			$newId = $this->write($oldId);

			if ($newId !== false)
			{
				if ($oldId !== false && $this->log)
				{
					$this->dataManager->log($this->getImportType(), $oldId, $newId);
				}

				$this->postSave($oldId, $newId);
			}
		}
		catch (\Exception $e)
		{
			if ($this->useTransaction)
			{
				$db->rollback();
			}

			throw $e;
		}

		if ($this->useTransaction)
		{
			$db->commit();
		}

		return $newId;
	}

	protected function preSave($oldId)
	{
		return null; // return false to prevent save
	}

	protected function postSave($oldId, $newId)
	{
	}

	public function convertToUtf8($string, $fromCharset = null, $convertHtml = null)
	{
		return $this->dataManager->convertToUtf8($string, $fromCharset, $convertHtml);
	}

	public function stripExtendedUtf8IfNeeded($string)
	{
		return $this->dataManager->stripExtendedUtf8IfNeeded($string);
	}

	public function convertToUtf8IfNeeded($string, $fromCharset = null, $convertHtml = null)
	{
		return $this->dataManager->convertToUtf8IfNeeded($string, $fromCharset, $convertHtml);
	}


	/**
	 * If (strval($string) === '') then replace it with the default text defined by $this->defaultText[$fieldType]
	 * in order to prevent validation / insertion errors.
	 *
	 * $contentId and $fieldName are there for future use, and represent the name of the content ID of the item to
	 * which this field belongs, and the name of the field, if different from the $fieldType string (often identical)
	 *
	 * @param      $string
	 * @param      $fieldType
	 * @param null $contentId
	 * @param null $fieldName
	 *
	 * @return mixed
	 */
	public function validTextOrDefault($string, $fieldType, $contentId = null, $fieldName = null)
	{
		if (trim(strval($string)) === '')
		{
			if (!isset($this->defaultText[$fieldType]))
			{
				$fieldType = 'message';
			}

			// TODO: perhaps make a session note saying that this text was defaulted using $oldId and $fieldName?
			/*if ($contentId !== null)
			{
				if ($fieldName === null)
				{
					$fieldName = $fieldType;
				}

				// save note...
			}*/

			return $this->defaultText[$fieldType];
		}

		return $string;
	}

	/**
	 * Allows extra data to be logged in the event that we need to map other content types to the inserted data
	 * such as 'node', 'node-url-fragment', $newId or 'blog_user', $blogId, $newNodeId
	 *
	 * @param $contentType (whatever we need to map from)
	 * @param $oldId
	 * @param $newId
	 */
	protected function logExtra($contentType, $oldId, $newId)
	{
		$this->dataManager->log($contentType, $oldId, $newId);
	}

	protected function insertMasterPhrase($title, $value, array $extra = [], $silent = false, $convertToUtf8 = true)
	{
		$phraseText = $convertToUtf8 ? $this->convertToUtf8IfNeeded($value) : $value;

		/** @var Phrase $phrase */
		$phrase = $this->dataManager->em()->create(Phrase::class);

		$phrase->title = $title;
		$phrase->phrase_text = (string) $phraseText;
		$phrase->language_id = 0;
		$phrase->addon_id = '';
		$phrase->bulkSet($extra);

		if ($phrase->save($silent ? false : true, false))
		{
			$this->dataManager->em()->detachEntity($phrase);
		}
	}

	protected function insertTemplate($title, $value, $type = 'public', $extra = [], $silent = true, $convertToUtf8 = true)
	{
		/** @var Template $template */
		$template = $this->dataManager->em()->create(Template::class);

		$template->title = $title;
		$template->type = $type;
		$template->style_id = 0;
		$template->addon_id = '';
		$template->setTemplateUnchecked($convertToUtf8 ? $this->convertToUtf8IfNeeded($value) : $value);
		$template->bulkSet($extra);

		$template->save($silent ? false : true, false);

		$this->dataManager->em()->detachEntity($template);
	}

	protected function importRawIp($userId, $contentType, $contentId, $action, $ip, $date)
	{
		$ip = Ip::stringToBinary($ip, false);
		if (!$ip)
		{
			return 0;
		}

		$this->db()->insert('xf_ip', [
			'user_id' => $userId,
			'content_type' => $contentType,
			'content_id' => $contentId,
			'action' => $action,
			'ip' => $ip,
			'log_date' => $date,
		]);

		return $this->db()->lastInsertId();
	}

	protected function insertCustomFieldValues($tableName, $contentColumn, $newId, array $customFields)
	{
		$insert = [];
		foreach ($customFields AS $key => $value)
		{
			$insert[] = [
				$contentColumn => $newId,
				'field_id' => $key,
				'field_value' => is_array($value) ? serialize($value) : $value,
			];
		}

		if ($insert)
		{
			$this->db()->insertBulk($tableName, $insert, false, 'field_value = VALUES(field_value)');
		}
	}

	public function __set($key, $value)
	{
		$this->set($key, $value);
	}

	public function __get($key)
	{
		return $this->get($key);
	}

	public function db()
	{
		return $this->dataManager->db();
	}

	/**
	 * @template T of \XF\Mvc\Entity\Repository
	 *
	 * @param class-string<T> $repo
	 *
	 * @return T
	 */
	public function repository($repo)
	{
		return $this->em()->getRepository($repo);
	}

	public function em()
	{
		return $this->dataManager->em();
	}

	public function app()
	{
		return $this->dataManager->app();
	}
}
