<?php

namespace XF\Cli\Command;

use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use XF\Db\Exception;
use XF\Db\Schema\Alter;

use function count;

class ConvertUtf8mb4 extends AbstractCommand
{
	protected function configure()
	{
		$this
			->setName('xf:convert-utf8mb4')
			->setDescription('Converts XenForo tables to utf8mb4');
	}

	protected $newCharset = 'utf8mb4';

	// TODO: this is using utf8mb4_general_ci most for BC reasons. Converting from utf8_general_ci to utf8mb4_unicode_ci
	// would be preferable, but it is not always viable due to more strings being considered identical. In future,
	// we may provide an option to use a different collation if people can manually deal with the issues.
	protected $newCollation = 'utf8mb4_general_ci';

	protected function execute(InputInterface $input, OutputInterface $output)
	{
		$db = \XF::db();

		$newCharset = $this->newCharset;
		$newCollation = $this->newCollation;

		$charset = $db->fetchRow("SHOW CHARACTER SET LIKE '{$newCharset}'");
		if (!$charset)
		{
			$output->writeln("<error>Your MySQL server does not support {$newCharset}. Please upgrade to a newer version.</error>");
			return 1;
		}

		$collation = $db->fetchAll("SHOW COLLATION LIKE '{$newCollation}'");
		if (!$collation)
		{
			$output->writeln("<error>Your MySQL server supports {$newCharset} but not {$newCollation}.</error>");
			return 1;
		}

		$regexParts = array_map(
			function ($v) { return preg_quote($v, '/'); },
			$this->getTablePrefixes()
		);
		$regexMatch = '/^(' . implode('|', $regexParts) . ')/';

		$convertable = [];
		foreach ($db->fetchAll("SHOW TABLE STATUS") AS $table)
		{
			$name = $table['Name'];
			if (!preg_match($regexMatch, $name))
			{
				continue;
			}

			if (!preg_match('/^utf8_/', $table['Collation']) && !preg_match('/^utf8mb3_/', $table['Collation']))
			{
				continue;
			}

			$convertable[] = $name;
		}

		if (!$convertable)
		{
			$output->writeln("No convertable tables found. No action required.");
			return 0;
		}

		$totalConvertable = count($convertable);

		$output->writeln([
			"There are {$totalConvertable} tables to convert.",
			'',
			'Conversion may be a time consuming process.',
			'<info>You must close your installation and take a backup before beginning the conversion process!</info>',
			'',
		]);

		/** @var QuestionHelper $helper */
		$helper = $this->getHelper('question');
		$question = new ConfirmationQuestion("<question>Are you ready to begin conversion? [y/n] </question>");
		if (!$helper->ask($input, $output, $question))
		{
			return 1;
		}

		$failed = [];

		$sm = $db->getSchemaManager();

		$output->writeln('Beginning conversion...');
		foreach ($convertable AS $i => $table)
		{
			$count = $i + 1;
			$output->write("[{$count}/{$totalConvertable}] $table... ");

			try
			{
				$sm->alterTable($table, function (Alter $alter) use ($newCharset, $newCollation)
				{
					$alter->convertCharset($newCharset, $newCollation);

					foreach ($alter->getColumnDefinitions() AS $column => $definition)
					{
						switch (strtolower($definition['Type']))
						{
							case 'char':
							case 'varchar':
							case 'tinytext':
							case 'text':
							case 'mediumtext':
							case 'longtext':
								$alter->changeColumn($column);
								break;
						}
					}
				});

				$output->write('Done', true);
			}
			catch (Exception $e)
			{
				$failed[$table] = $e->getMessage();
				$output->write('<error>Failed</error>', true);
			}
		}

		if ($failed)
		{
			$output->writeln([
				'',
				'<error>The following tables failed to convert:</error>',
			]);
			foreach ($failed AS $table => $error)
			{
				$output->writeln("\t* $table: $error");
			}
			$output->writeln('<error>You should contact the table creator for guidance. Failure to correct this may lead to unexpected behavior.</error>');
		}

		$messages = [
			'',
			$failed ? 'Conversion complete, but with errors!' : 'Conversion complete!',
		];

		if (!\XF::app()->config('fullUnicode'))
		{
			$messages = array_merge($messages, [
				"<info>You must now add the following to your src/config.php file:</info>",
				"<info>\$config['fullUnicode'] = true;</info>",
			]);
		}

		$output->writeln($messages);

		return 0;
	}

	protected function getTablePrefixes()
	{
		return ['xf_', 'xengallery_'];
	}
}
