<?php

namespace Win7;

use DI\Container;
use DI\ContainerBuilder;
use Illuminate\Database\Capsule\Manager as Capsule;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ServerRequestInterface as Request;
use RuntimeException;
use Slim\Factory\ServerRequestCreatorFactory;
use Win7\Utils\ArrayUtils;

/**
 * @author Thiago Daher
 */
final class App
{

    public const VERSION = '1.0.0.6';

    /**
     * @var Container
     */
    private static Container $container;

    /**
     * @var Config
     */
    private static Config $config;

    /**
     * @var bool
     */
    private static bool $initialized = false;

    /**
     * @var Request
     */
    private static Request $request;

    /**
     * @return Container
     */
    public static function getContainer(): Container
    {
        return self::$container;
    }

    /**
     * @return void
     * @throws \Exception
     */
    public static function initialize()
    {
        if (self::$initialized) {
            return;
        }

        self::$initialized = true;
        self::defineConstants();
        self::initServerRequest();
        self::initConfig();
        self::initContainer();
        self::initEloquent();

        if (!self::isConsole()) {
            self::$container->set(ServerRequestInterface::class, self::$request);
        }
    }

    /**
     * @return void
     */
    private static function initConfig()
    {
        $appEnv = getenv('APP_ENV');

        if ($appEnv) {
            $appEnv = strtolower($appEnv);
        }

        $envConfig = $appEnv === 'dev' ? self::loadArrayFile(CONFIG_PATH . 'dev.php') : [];
        $defaultConfig = self::loadArrayFile(CONFIG_PATH . 'default.php');
        self::$config = new Config(ArrayUtils::arrayMergeRecursiveDistinct($defaultConfig, $envConfig));
    }

    /**
     * Inicializa o objeto que guarda as informações da requisição.
     *
     * @return void
     */
    private static function initServerRequest()
    {
        if (self::isConsole()) {
            return;
        }

        $serverRequestCreator = ServerRequestCreatorFactory::create();
        self::$request = $serverRequestCreator->createServerRequestFromGlobals();
    }

    /**
     * @return string
     */
    public static function getAppType(): string
    {
        return php_sapi_name() == 'cli' ? 'console' : 'http';
    }

    /**
     * @return bool
     */
    public static function isConsole(): bool
    {
        return self::getAppType() == 'console';
    }

    /**
     * @return void
     */
    private static function initEloquent()
    {
        $settings = self::getConfig()->get('database');
        $capsule = new Capsule();

        foreach ($settings as $name => $configs) {
            $capsule->addConnection($configs, $name);
        }

        $capsule->setAsGlobal();
        $capsule->bootEloquent();
    }

    /**
     * @return void
     */
    private static function defineConstants()
    {
        define('CONFIG_PATH', realpath(__DIR__ . '/../config/') . '/');
        define('RESOURCES_PATH', realpath(__DIR__ . '/../resources/') . '/');
    }

    /**
     * @param string $path
     * @return array
     */
    private static function loadArrayFile(string $path): array
    {
        $result = self::loadFile($path);

        if (!is_array($result)) {
            throw new RuntimeException("The file " . basename($path) . ' is not an configuration file!');
        }

        return $result;
    }

    /**
     * @param string $path
     * @return mixed|array|callable
     */
    private static function loadFile(string $path)
    {
        if (!file_exists($path)) {
            throw new RuntimeException("The file " . basename($path) . ' does not exist!');
        }

        return require $path;
    }

    /**
     * @return void
     * @throws \Exception
     */
    private static function initContainer()
    {
        $builder = new ContainerBuilder();
        $builder->useAnnotations(true);
        $builder->addDefinitions(self::loadArrayFile(CONFIG_PATH . 'dependencies.php'));
        self::$container = $builder->build();
        self::$container->set(Config::class, self::$config);
    }

    /**
     * @return Config
     */
    public static function getConfig(): Config
    {
        return self::$config;
    }
}
