<?php

namespace Snaju\Inception;

use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Dotenv\Dotenv;
use Snaju\Inception\Annotation\AnnotationManager;
use Snaju\Inception\Event\EventManager;
use Snaju\Inception\Event\Types\OnAppReadyEvent;
use Snaju\Inception\Event\Types\OnExceptionEvent;
use Snaju\Inception\Event\Types\OnRouteStart;
use Snaju\Inception\Exception\Exception;
use Snaju\Inception\ORM\ORM;
use Snaju\Inception\Plugin\PluginManager;
use Snaju\Inception\Route\Loaders\Func\FuncRouteLoader;
use Snaju\Inception\Route\Loaders\Twig\TemplateManager;
use Snaju\Inception\Route\Loaders\Twig\TwigRouteLoader;
use Snaju\Inception\Http\Middleware\MiddlewareRegistry;
use Snaju\Inception\Http\Request\WebRequest;
use Snaju\Inception\Route\RouteManager;
use Snaju\Inception\Util\Loader;

// TODO: add docs
class InceptionCore
{

    private static string $base = __DIR__;

    public static string $appRoot;

    public static MiddlewareRegistry $middleware;

    private static WebRequest $request;

    private static $ENV;

    public static function getBase(): string
    {
        return self::$base;
    }

//    public static function getAppRoot(): string
//    {
//        return self::$appRoot;
//    }

    public static function initBase($base = __DIR__)
    {
        self::$base = $base;
    }

    /**
     * @return WebRequest
     */
    public static function &getRequest(): WebRequest
    {
        return self::$request;
    }

    /**
     * @return mixed
     */
    public static function &getENV()
    {
        return self::$ENV;
    }

    public static function init($base, bool $render = true): void
    {
        self::$ENV = Dotenv::createImmutable($base);
        self::$ENV->safeLoad();
        self::initBase($base);
        self::$request = new WebRequest($render);
        self::$middleware = new MiddlewareRegistry();

        AnnotationManager::init();

        // Set Exception Handler to use Event/Hook System.
        set_exception_handler(function ($exception) {
            (new OnExceptionEvent($exception))->call();
        });

        // Include and declare global helper functions
        include_once 'Util/GlobalHelpers.php';

        // Initialize the route tree, add default loaders and init route manager
        RouteManager::initTree();

        /*
         * Load Plugins
         * */
        PluginManager::init();

        foreach (PluginManager::$plugins as $plugin) {
            PluginManager::loadIfNotAlready($plugin);
        }

        // Add Twig Route Loader
        if ((bool)config('app.useTwigRendering')) {
            RouteManager::addLoader(new TwigRouteLoader());
        }

        // Add Func Route Loader
        RouteManager::addLoader(new FuncRouteLoader());

        // Init the RouteManager
        if ($render) {
            RouteManager::init();
        }

        // Setup appRoot
        if (config('app.rootDirectory') != null) {
            self::$appRoot = $base . "/" . config('app.rootDirectory');
        } else {
            // Not set check if /app exist
            if (file_exists($base . "/" . "app")) {
                // It does exist, this will be the root
                self::$appRoot = $base . "/" . "app";
            } else {
                throw new \Exception("Failed to locate app root in (" . $base . "/" . "app)");
            }
        }

        // Load all the files in /app
        $loader = new Loader(self::$appRoot, '/.*\.php/mi', false);
        foreach ($loader->getMatches() as $m) {
            require_once $m;
        }

        // Load Event Hooks from Methods
//        EventManager::registerHooksFromMethods();

        /*
         * Start Session
         * */
        if ($render) {
            session_start();
        }

        // Load ORM if enabled
        if (file_exists(self::$appRoot . "/" . config('database.modelsDirectory'))) {
            ORM::addModelLocation(self::$appRoot . "/" . config('database.modelsDirectory'));
        }

        // Setup and initialize the ORM
        if (config('database.enableORM')) {
            // Setup the Database
            $dbConfig = ORM::init();
            $dbConfig->setAutoGenerateProxyClasses(true);
            $dbConfig->setProxyDir(self::$base . "/tmp");
//            $dbConfig->setMetadataDriverImpl(new AttributeDriver());

            if (ORM::$cache != null) {
//                $dbConfig->setQueryCacheImpl(ORM::$cache);
//                $dbConfig->setResultCacheImpl(ORM::$cache);
                $dbConfig->setMetadataCacheImpl(ORM::$cache);
            }

            if (env("DB_URL") != null) {
                ORM::connect([
                    "url" => env("DB_URL")
                ], $dbConfig);
            } else {
                ORM::connect([
                    "dbname" => env('DB_NAME'),
                    "user" => env('DB_USER'),
                    "password" => env('DB_PASS'),
                    "host" => env('DB_HOST'),
                    "port" => env("DB_PORT"),
                    "driver" => (env("DB_DRIVER") != null) ? env("DB_DRIVER") : "pdo_mysql",
                    "server_version" => env("DB_VERSION")
                ], $dbConfig);
            }

            if (config('database.autoSyncing')) {
                ORM::updateSchema();
            }
        }

        /*
         * Call App Ready Event
         * */
        (new OnAppReadyEvent())->call();

        /*
         * Render the route based on the route that was selected in tree.
         * */

        if ($render) {
            TemplateManager::addData("http", self::$request->getAll());
        }

        if ($render) {
            (new OnRouteStart(self::$request->getUri()->getPath()))->call();
            exit(200);
        }
    }
}

