<?php

namespace apexl\Io\modules\install\controllers;

use apexl\Config\Configuration as Config;
use apexl\hashing\Hash;
use apexl\Io\includes\System;
use apexl\Io\modules\install\services\databaseTools;
use apexl\Io\modules\install\services\Install;
use apexl\Io\services\Output;
use apexl\Io\services\pathUtility;
use apexl\Vault\Vault;
use Exception;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

final readonly class automaticController
{
    public function __construct(
        private pathUtility $path,
        private Vault $database,
        private Config $config,
        private databaseTools $dbTools,
        private Install $install,
    ) {
    }

    /**
     * @throws Exception
     * @method POST
     * Automatic install method. Runs the core IO installation process, requires Database user and pass as a minimum to run.
     */
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
    {
        //@see bodyParserController.php for parsed Json.
        $body = $request->getParsedBody();
        //protect install with a key
        if (!isset($this->config->app->install->key) || empty($this->config->app->install->key)) {
            throw new Exception("There is no configuration Key set in config :: 'app->install->key' Is missing.");
        }

        if (!isset($body->install_key)) {
            throw new Exception("You must provide 'install_key' in the request body");
        }

        if ($body->install_key !== $this->config->app->install->key) {
            throw new Exception(
                "The provided Installation key || ".isset($body->install_key) !== '' ? $body->install_key : 'Unknown Key || does not match expected key.'
            );
        }

        //check if we have the username / pass for the database already. if we do, skip the update of the config.
        $this->setupConfig($body);
        //loop over the provided databases and install.
        $exists = $this->dbTools->checkDBExists();

        foreach (array_keys($body->databases) as $dbName) {
            //Check we can make a connection?.
            if (!$exists) {
                //No connection? we better try to create the database.
                $database = $this->database->getDatabase();
                //Start by unsetting the stored database so it's not added to the PDO connection string.
                $this->database->setDatabase();
                //use the Db we had stored and attempt to create it.
                $this->database->createDatabase($database);
                //no 500's? assume it worked, reset the database and test it again.
                //write new name to config before we reload.
                System::writeConfig('vault', ['databases' => [$dbName => ['dbname' => $database]]]);
                $this->database->setDatabase($database);
                $exists = $this->dbTools->checkDBExists($dbName);
                //next we check if the new DB exists. If it does, we need to check config for new users or just assign the existing one.
                if (!$exists) {
                    throw new Exception('Cannot connect to, or create provided database.');
                }

                if (isset($body->create_new_database_user_on_install) && $body->create_new_database_user_on_install) {
                    //create a new user / password, overwrite our current known details
                    $user = "io_user_".$database."_".str_replace(['.', ' '], '', microtime());
                    $pass = (new Hash())->generateRandomHash();
                    $this->database->createUser($user, $pass);
                    $this->database->assignUserToDB($user, $database);

                    //write new config to the config file.
                    System::writeConfig(
                        'vault',
                        ['databases' => [$dbName => ['dbuser' => $user, 'dbpass' => $pass]]]
                    );
                } else {
                    //Catch GRANT errors but continue anyway, as we may be able to.
                    try {
                        $this->database->assignUserToDB(
                            Config::getInstance()->vault->databases->$dbName->dbuser,
                            $database
                        );
                    } catch (Exception $e) {
                        Output::addSystemError($e);
                    }
                }
            }
            //re-initialise values as we've just written the content to file.
            $this->database->initiateVaults();

            $this->install->installModule(
                'install'
            ); //install the install module (namely this is the main site one).
            $this->install->installModule('authentication');
            $this->install->installModule('system');
        }

        return System::asJson($response, ["message" => "Installation Completed."]);

    }

    protected function setupConfig($body)
    {
        if (isset($body->databases)) {
            //load any existing config first.
            $config = Config::getInstance();
            //check for vault key. If it's missing, add it.
            if ($config->vault ?? false) {
                System::writeConfig('vault', ['databases' => []]);
            }
            foreach ($body->databases as $key => $values) {
                //first check for config in the body.
                $host = $body->databases->$key->host ?? null;
                $port = $body->databases->$key->port ?? null;
                $dbname = $body->databases->$key->dbname ?? null;
                $driver = $body->databases->$key->driver ?? null;
                $operator = $body->databases->$key->operator ?? null;
                $user = $body->databases->$key->user ?? null;
                $pass = $body->databases->$key->pass ?? null;

                //next for existing database config
                //next, check if
                if (($config->vault->databases ?? false) && ($config->vault->databases->$key ?? false)) {
                    $existingHost = $config->vault->databases->$key->host ?? null;
                    $existingPort = $config->vault->databases->$key->port ?? null;
                    $existingDbname = $config->vault->databases->$key->dbname ?? null;
                    $existingDriver = $config->vault->databases->$key->driver ?? null;
                    $existingOperator = $config->vault->databases->$key->operator ?? null;
                    $existingUser = $config->vault->databases->$key->user ?? null;
                    $existingPass = $config->vault->databases->$key->pass ?? null;
                }

                //next we check for any missing values and add defaults.
                $config = [
                    'dbhost' => $host ?? $existingHost ?? 'localhost',
                    'port' => $port ?? $existingPort ?? '3306',
                    'dbname' => $dbname ?? $existingDbname ?? 'io_default_'.str_replace(['.', ' '], '', microtime()),
                    'driver' => $driver ?? $existingDriver ?? 'mysql',
                    'operator' => $operator ?? $existingOperator ?? 'pdo',
                    'dbuser' => $user ?? $existingUser ?? '',
                    'dbpass' => $pass ?? $existingPass ?? '',
                ];

                System::writeConfig('vault', ['databases' => [$key => $config]]);
                (Config::getInstance())->reloadConfig();
                //force vault to reinitialise
                $vault = Vault::getInstance();
                $vault->initiateVaults();
            }
        }
    }

}
