<?php

/**
 * Vvveb
 *
 * Copyright (C) 2022  Ziadin Givan
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

namespace Vvveb\Controller;

use function Vvveb\__;
use function Vvveb\getTemplateList;
use function Vvveb\sanitizeFileName;
use function Vvveb\session as sess;
use Vvveb\Sql\LanguageSQL;
use Vvveb\Sql\Menu_itemSQL;
use Vvveb\Sql\RoleSQL;
use Vvveb\Sql\SiteSQL;
use Vvveb\System\Core\View;
use Vvveb\System\Extensions\Plugins;
use Vvveb\System\Extensions\Themes as ThemesList;
use Vvveb\System\Functions\Str;
use Vvveb\System\Locale;
use Vvveb\System\Media\Image;
use Vvveb\System\User\Admin;

define('REQUIRED_EXTENSIONS', ['mysqli', 'mysqlnd', 'xml', 'libxml', 'pcre',  'zip', 'dom', 'curl', 'gettext']);
define('WRITABLE_FOLDERS', ['storage', 'storage/cache', 'storage/model', 'storage/compiled-templates', 'config', 'config/sites.php', 'public/media/', 'public/themes', 'public/image-cache', 'plugins']);
define('MIN_PHP_VERSION', '8.3.0');
define('DEFAULT_LANG', 'en_US');

#[\AllowDynamicProperties]
class Index extends Base {
	/** @var array{engine: string, host: string, database: string, user: string, password: string, port: string|null, prefix: string} */
	private array $config = ['engine' => 'mysqli', 'host' => '127.0.0.1', 'database'  => 'vvveb', 'user'  => 'root', 'password'  => '', 'port'  => null, 'prefix'  => ''];

	/** @var string */
	private string $subdir = '';

	public function __construct() {
		if (! ($lang = sess('install_language'))) {
			$lang = Locale::userPreferedLanguage();

			if ($lang) {
				sess(['install_language' => $lang, 'language_id' => 1]);
			}
		}

		if ($lang) {
			Locale::setLanguage($lang);
		}

		$this->subdir = (V_SUBDIR_INSTALL ? V_SUBDIR_INSTALL : \Vvveb\detectSubDir());

		if (\Vvveb\is_installed()) {
			$admin   = false;
			$message = '';

			try {
				//check if super admin user is available
				$admin = Admin::get(['role_id' => 1]);
			} catch (\Exception $e) {
				$this->view         = View :: getInstance();
				$message            = __('Missing admin table or data, remove config/db.php to reinstall!') . "\n\n" . $e->getMessage();
				$this->view->info[] = $message;

				//die($message);
			}

			if ($admin && isset($admin['status']) && $admin['status'] == '1') {
				$this->redirect($this->subdir . '/');

				die(__('Already installed! To reinstall remove config/db.php') . "\n");
			} else {
				if (! $admin || ! isset($admin['role_id']) || $admin['role_id'] != '1') {
					$message .= __('Invalid installation. No user with "super admin" role found!');
					$this->view         = View :: getInstance();
					$this->view->info[] = $message;
				}
			}
		}
	}

	/**
	 * Check PHP requirements: version, extensions, and writable folders.
	 *
	 * @return array<int, string> List of unmet requirements.
	 */
	private function checkRequirements(): array {
		$notMet = [];

		if (version_compare(PHP_VERSION, MIN_PHP_VERSION) < 0) {
			$notMet[] = sprintf(__('You need at least PHP %s , your current version %s'), MIN_PHP_VERSION, PHP_VERSION);
		}

		foreach (REQUIRED_EXTENSIONS as $extension) {
			if (! extension_loaded($extension)) {
				$notMet[] = sprintf(__('PHP extension %s is not installed'), $extension);
			}
		}

		foreach (WRITABLE_FOLDERS as $folder) {
			$path = DIR_ROOT . $folder;

			if (! is_writable($path) && ! @chmod($path, 0750)) {
				$notMet[] = sprintf(__('"%s" is not writable'), $folder);
			}
		}

		return $notMet;
	}

	/**
	 * Replace a string in a file.
	 *
	 * @param  string          $file    The file path.
	 * @param  string|string[] $search  The string(s) to search for.
	 * @param  string|string[] $replace The replacement string(s).
	 * @return int|false       Number of bytes written, or false on failure.
	 */
	private function replaceInFile(string $file, string|array $search, string|array $replace): int|false {
		$content = file_get_contents($file);

		if ($content) {
			$content = str_replace($search, $replace, $content);

			return file_put_contents($file, $content);
		}

		return false;
	}

	/**
	 * Write database configuration.
	 *
	 * @param  array<string, mixed> $data The configuration data.
	 * @return mixed
	 */
	private function writeConfig(array $data): mixed {
		return \Vvveb\setConfig('db', $data);
		$configFile = DIR_ROOT . 'config/db.php';
		file_put_contents($configFile, "<?php\n return " . var_export($data, true) . ';');
		clearstatcache(true, $configFile);
	}

	/**
	 * Import database schema and data from POST parameters.
	 *
	 * @param  bool $noimport Whether to skip data import.
	 * @return void
	 */
	private function import(bool $noimport = false): void {
		$config = $this->config;

		array_walk($this->request->post, function ($value, $key) use (&$config) {
			if (isset($config[$key])) {
				$config[$key] = $value;
			}
		});

		$config['engine'] = $config['engine'] ? $config['engine'] : 'mysqli';

		if ($config['engine'] === 'sqlite') {
			$config['host'] = DIR_STORAGE . 'sqlite/vvveb.db';
		}

		extract($config);

		$prefix                       = $prefix ?? '';
		$data['default']              = $config['engine'];
		$data['connections'][$engine] = $config;

		try {
			if (! defined('DB_ENGINE')) {
				define('DB_ENGINE', $engine);
				define('DB_HOST',   $host);
				define('DB_USER',   $user);
				define('DB_PASS',   $password);
				define('DB_NAME',   $database);
				define('DB_PREFIX', $prefix);
				define('DB_PORT',   $port);
				define('DIR_SQL',   DIR_APP . 'sql/' . DB_ENGINE . '/');
			}

			$import = new \Vvveb\System\Import\Sql($engine, $host, $database, $user, $password, $port, $prefix);
			//$import->createDb($database);
			if ($engine === 'mysqli') {
				$import->createDb($database);
			}
			$import->setPath(DIR_ROOT . "install/sql/$engine/schema/");
			$import->createTables();

			if ($noimport) {
				$exclude = ['post*', 'product*', 'vendor*', 'manufacturer*', 'taxonomy_*', 'attribute*', 'option*', 'user.sql', 'comment*', 'digital_asset*'];
			} else {
				$exclude = [];
			}

			$import->setPath(DIR_ROOT . 'install/sql/insert/');
			$import->insertData([], $exclude);
			//$import->db->close();
			$this->writeConfig($data);

			$this->redirect(($_SERVER['REQUEST_URI'] ?? 'localhost') . '?action=install');
		} catch (\Exception $e) {
			$this->view->errors[] = sprintf(__('Db error: "%s" Error code: "%s"'), $e->getMessage(), $e->getCode());
		}
	}

	/**
	 * Get available templates for a given theme.
	 *
	 * @param  string                $theme The theme slug.
	 * @return array<string, string> Associative array of filename => title.
	 */
	private function themeTemplates(string $theme): array {
		$templates = getTemplateList($theme);

		$files = ['index.html' => 'Home page'];
		foreach ($templates as $template) {
			foreach (['error', 'index.'] as $type) {
				if (str_contains($template['filename'], $type)) {
					continue 2;
				}
			}

			$files[$template['filename']] = $template['title'];
			if ($template['folder'] !== null) {
				break;
			}
		}

		return $files;
	}

	/**
	 * Return theme templates as JSON for AJAX requests.
	 *
	 * @return array<string, string>|null
	 */
	public function getThemeTemplates(): ?array {
		$theme = $this->request->get['theme'];
		if ($theme) {
			$files = $this->themeTemplates($theme);

			$this->response->setType('json');
			return $files;
		}

		return null;
	}

	/**
	 * Main installer page — step 1: database configuration and language selection.
	 *
	 * @return void
	 */
	public function index(): void {
		$requirements   = $this->checkRequirements();
		$noimport 	     = $this->request->post['noimport'] ?? false;

		if ($requirements) {
			$this->view->requirements     = $requirements;
		}

		\Vvveb\System\CacheManager::clearFrontend();

		//get defaults from get parameters if passed or from env if available
		foreach ($this->config as $key => &$value) {
			$env = 'DB_' . strtoupper($key);

			if (isset($_ENV[$env])) {
				$value = $_ENV[$env];
			}

			if (isset($this->request->get[$key])) {
				$value = $this->request->get[$key];
			}
		}

		if ($this->request->post) {
			if (isset($this->request->post['language'])) {
				$lang = $this->request->post['language'];
				sess(['install_language' => $lang, 'language_id' => 1]);
				Locale::setLanguage($lang);
			} else {
				$this->import($noimport);
				//if user data is provided (by CLI) run also step2
				if (isset($this->request->post['admin'])) {
					$this->install();
				}
			}
		}

		$installedLanguages = [DEFAULT_LANG => '0'] + array_flip(Locale::installedLanguages());
		$languagesList      = include DIR_SYSTEM . 'data/languages-list.php';
		$languages          = array_intersect_key($languagesList, $installedLanguages);
		$this->view->config = $this->config;

		if (! defined('CLI')) {
			$this->view->languagesList    = $languages;
			$this->view->currentLanguage  = sess('install_language') ?? DEFAULT_LANG;
		}
	}

	/**
	 * Main installer page — step 2: admin user, site settings, theme, and final setup.
	 *
	 * @return void
	 */
	public function install(): void {
		if (! defined('CLI')) {
			$themes      =  ThemesList :: getList();
			$activeTheme = key($themes);

			$this->view->themes    = $themes;
			$this->view->templates = $this->themeTemplates($activeTheme);

			$this->view->count  = count($themes);
		}

		$isRootPublic             = (constant('PUBLIC_PATH') === DIRECTORY_SEPARATOR) ? 'true' : 'false';
		$this->view->publicPath   = PUBLIC_PATH;
		$this->view->isRootPublic = $isRootPublic;

		$languagesList      = include DIR_SYSTEM . 'data/languages-list.php';

		if (! defined('CLI')) {
			$this->view->languagesList    = $languagesList;
			$this->view->currentLanguage  = sess('install_language') ?? DEFAULT_LANG;
		}

		if ($this->request->post) {
			//set admin password
			$user        = $this->request->post['admin'] ?? [];
			$settings    = $this->request->post['settings'] ?? [];
			$theme       = $this->request->post['theme'] ?? 'landing';
			$homepage    = $this->request->post['homepage'] ?? 'index.html';
			$noecommerce = $this->request->post['noecommerce'] ?? false;
			$hostname    = $this->request->post['hostname'] ?? null;
			$adminPath   = $this->request->post['admin-path'] ?? false;
			$language    = $this->request->post['language'] ?? 'en_US';
			$languages   = $this->request->post['languages'] ?? [];

			$user['status'] = 1;
			$result         = Admin::update($user, ['username' => 'admin']);
			$sites          = new SiteSQL();
			//$result         = \Vvveb\setMultiSetting('site',$settings);

			//set default website url
			$sites           = new SiteSQL();
			$siteSettings    = $sites->get(['site_id' => 1]);
			$hasSiteSettings = false;

			if (is_array($settings) && is_array($siteSettings)) {
				$hasSiteSettings = true;
				$data            = json_decode($siteSettings['settings'], true) ?? [];
				$settings        = array_replace_recursive($data, $settings);
			}

			$settings['admin-email']   = $user['email'];
			$settings['contact-email'] = $user['email'];

			if (Image::formats('webp')) {
				$settings['image_format'] = 'webp';
			}

			$site = [
				'host'     => $hostname ?? '*.*.*', //$_SERVER['HTTP_HOST']
				'site_id'  => 1,
				'name'     => $settings['description'][1]['title'] ?? 'Default',
				'theme'    => $theme,
				'template' => [$homepage],
				'settings' => json_encode($settings),
			];

			if ($hasSiteSettings) {
				$sites->edit(['site' => $site, 'site_id' => 1]);
			} else {
				//empty installation
				$site['site_id'] = 1;
				$site['key']     = '* * *';
				$site['name']    = 'default';
				$sites->add(['site' => $site]);

				$role = new RoleSQL();
				$role->add(['role' => [
					'role_id'      => 1,
					'name'         => 'super_admin',
					'display_name' => 'Super Administrator',
					'permissions'  => '{"allow":["*"], "deny":[]}',
				]]);

				$languageModel = new LanguageSQL();
				$languageModel->add(['language' => [
					'language_id' => 1,
					'name'        => 'English',
					'code'        => 'en_US',
					'locale'      => 'en-us',
					'slug'        => 'en',
					'status'      => 1,
					'default'     => 1,
				]]);
			}

			unset($site['settings']);
			@\Vvveb\setConfig('sites.* * *', $site);

			$lang = $language ?? sess('install_language') ?? 'en_US';
			$languageModel      = new LanguageSQL();

			//set default language
			if ($lang && $lang !== 'en_US' && isset($languagesList[$lang])) {
				$language           = $languagesList[$lang];
				$language['name']   = preg_replace('/^(\w+).*/', '$1', $language['name']);
				$language['slug']   = preg_replace('/^(\w+).*/', '$1', $language['code']);
				$language['locale'] = $language['code'];
				$language['code']   = $lang;
				$language['status'] = 1;
				//$installed              = $languageModel->get(['code' => $lang]);
				$result = $languageModel->edit(['language' => $language, 'language_id' => 1]);
				sess(['language' => $language['slug'], 'code' => $language['code'], 'language_id' => 1]);
				Locale::setLanguage($lang);
			}

			$inserted = [$lang];
			if ($languages) {
				foreach ($languages as $code) {
					if (in_array($code, $inserted)) {
						continue;
					}

					$inserted[]   = $code;
					$ln           = $languagesList[$code] ?? null;
					if (! $ln) { continue; }
					$ln['name']   = preg_replace('/^(\w+).*/', '$1', $ln['name']);
					$ln['slug']   = preg_replace('/^(\w+).*/', '$1', $ln['code']);
					$ln['locale'] = $ln['code'];
					$ln['code']   = $code;
					$ln['status'] = 1;

					$languageModel->add(['language' => $ln]);
				}
			}

			$error = '';

			//change admin login path
			if ($isRootPublic && $adminPath &&
				($adminPath !== 'admin' && $adminPath !== 'vadmin')) {
				$from = DIR_PUBLIC . 'vadmin';
				$to   = DIR_PUBLIC . $adminPath;

				if (@rename($from, $to)) {
					@\Vvveb\setConfig('admin.path', $adminPath);
					//if succesful remove failsafe /admin login option
					@unlink(DIR_PUBLIC . 'admin' . DS . 'index.php');
				} else {
					$error = sprintf(__('Renaming admin login path from %s to %s failed! use /admin/index.php path to login'), 'vadmin', $adminPath);
				}
			}

			@\Vvveb\setConfig('app.cronkey', Str::random(32));
			@\Vvveb\setConfig('app.key', Str::random(32));

			//set APCu memory cache if available instead of default file cache
			$cacheDriver = (function_exists('apcu_cache_info') && ini_get('apc.enabled')) ? 'APCu' : null;

			foreach (['app', 'admin', 'graphql', 'rest'] as $app) {
				if ($cacheDriver) {
					@\Vvveb\setConfig($app . '.cache.driver', $cacheDriver);
				}
				@\Vvveb\setConfig($app . '.cronkey', Str::random(32));
				@\Vvveb\setConfig($app . '.key', Str::random(32));
			}

			$subdir = $this->request->post['subdir'] ?? $this->subdir ?? false;

			if ($subdir) {
				$subdir = sanitizeFileName($subdir);
				$subdir = str_replace(['\\', '/'], '/', $subdir);
				$subdir = '/' . trim($subdir, '/ ');

				//try to set subdir in env.php and .htaccess
				$this->replaceInFile(DIR_ROOT . 'env.php', "define('V_SUBDIR_INSTALL', false" , "define('V_SUBDIR_INSTALL', '{$subdir}'");
				$this->replaceInFile(DIR_ROOT . '.htaccess', 'RewriteRule ^ index.php [L]' , "RewriteRule ^ {$subdir}/index.php [L]");
			}

			$menus = new Menu_itemSQL();

			if ($subdir || $isRootPublic == 'false') {
				//add subdir path to menu links
				$sub   = $subdir ?: '';

				foreach ([1, 5] as $menu_id) { //main menu and footer menu id's
					$menuItems = $menus->getAll(['menu_id' => $menu_id, 'language_id' => 1, 'site_id' => 1])['menu_item'] ?? [];

					foreach ($menuItems as $menuItem) {
						$data = ['url' => $sub . $menuItem['url'], 'menu_item_content' => []];
						if ($menuItem['content']) {
							$content = str_replace('"/media/', '"' . PUBLIC_PATH . 'media/', $menuItem['content']);
							$data['menu_item_content'] = [['content' => $content, 'language_id' => 1]];
						}

						$menus->editMenuItem(['menu_item' => $data,  'menu_item_id' => $menuItem['menu_item_id']]);
					}
				}
			}

			if ($noecommerce) {
				Plugins::activate('hide-ecommerce', 1);
				//delete shop menu item
				$menus->deleteMenuItem(['menu_item_id' => 43]);
			}

			if ($error) {
				$this->view->error[] = $error;
			}

			//admin auto login
			if (! defined('CLI')) {
				$adminInfo = Admin::get(['admin_id' => 1]);

				if ($adminInfo) {
					\Vvveb\session(['admin' => $adminInfo]);
				}
			}

			$success               = __('Installation succesful!');
			$this->view->success[] = $success;
			$admin_path            = \Vvveb\adminPath();
			$admin_path            = str_replace($this->subdir, '', $admin_path);
			$location              = preg_replace('@/install.*$@', $admin_path . "/index.php?module=settings/site&site_id=1&success=$success&errors=$error#description-tab", ($_SERVER['REQUEST_URI'] ?? ''));

			$this->redirect($location);
		}

		$this->view->template('install.html');
	}
}
