<?php

namespace apexl\Io\modules\install;

use apexl\Config\Singleton;
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 Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;

class installController
{
    public function __construct(
        public pathUtility $path,
        protected Vault $database,
        protected Singleton $config,
        protected databaseTools $dbTools,
        protected Install $install,
    ) {
    }

    /**
     * @method GET
     * @return Response
     */
    public function install(Request $request, Response $response)
    {
        //for now, we return the install options so any system interacting with this can make a choice.
        $options = (object) [
            'installOptions' => (object) [
                'automatic',
            ],
        ];

        //@todo add function to allow for additional install options to be added.
        return System::asJson($response, $options);
    }

    /**
     * @return Response
     * @method GET
     */
    public function list(Request $request, Response $response)
    {
        //list all modules and their install states
        return System::asJson($response, $list);
    }

    /**
     * @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 automatic(Request $request, Response $response)
    {
        //@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 (isset($body->install_key) && $body->install_key == $this->config->app->install->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.
            foreach ($body->databases as $key => $values) {
                //Check we can make a connection?.
                $exists = $this->dbTools->checkDBExists();
                if (!$exists) {
                    //No connection? we better try 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.
                    System::writeConfig('vault', ['databases' => [$key => ['dbname' => $database]]]
                    ); //write new name to config before we reload.
                    $this->database->setDatabase($database);
                    $exists = $this->dbTools->checkDBExists($key);
                    //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) {
                        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' => [$key => ['dbuser' => $user, 'dbpass' => $pass]]]
                            );
                        } else {
                            //Catch GRANT errors but continue anyway, as we may be able to.
                            try {
                                $this->database->assignUserToDB(
                                    (Singleton::getInstance())->vault->databases->$key->dbuser,
                                    $database
                                );
                            } catch (\Exception $e) {
                                Output::addSystemError($e);
                            }
                        }
                    } else {
                        throw new \Exception('Cannot connect to, or create provided database.');
                    }
                }
                //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."]);
        } else {
            throw new \Exception(
                "The provided Installation key || ".isset($body->install_key) !== '' ? $body->install_key : 'Unknown Key || does not match expected key.'
            );
        }
    }

    protected function setupConfig($body)
    {
        if (isset($body->databases)) {
            //load any existing config first.
            $config = Singleton::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]]);
                (Singleton::getInstance())->reloadConfig();
                //force vault to reinitialise
                $vault = Vault::getInstance();
                $vault->initiateVaults();
            }
        }
    }

    public function installModule(Request $request, Response $response)
    {
        $body = $request->getParsedBody();
        if (isset($body->install_key) && $body->install_key == $this->config->app->install->key) {
            if (isset($body->modules) && !empty($body->modules)) {
                foreach ($body->modules as $name) {
                    $this->install->installModule($name);
                }
            }
        } else {
            throw new \Exception(
                "The provided Installation key || ".isset($body->install_key) !== '' ? $body->install_key : 'Unknown Key || does not match expected key.'
            );
        }

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