<?php

namespace App\Http\Middleware;

use App\DataMigration;
use App\Facade\UserConfig;
use App\Helpers\MiscHelper;
use Closure;
use Illuminate\Foundation\Application;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Log;

class AppInstallation
{
    private $baseDirectory;
    private $environmentFilePath;

    /**
     * The application instance.
     *
     * @var \Illuminate\Foundation\Application
     */
    protected $app;

    /**
     * Create a new middleware instance.
     *
     * @param  \Illuminate\Foundation\Application  $app
     * @return void
     */
    public function __construct(Application $app)
    {
        $this->app = $app;
        $this->baseDirectory = dirname(dirname(dirname(__DIR__)));
        $this->environmentFilePath = sprintf('%s/.env', $this->baseDirectory);
    }

    public function handle(Request $request, Closure $next)
    {
        // We always need an encryption key if not already present
        $this->generateAppKey();

        if ($this->app->runningInConsole())
        {
            // Allow the console to run successfully even if we're not installed
            return $next($request);
        }

        // See if the successful flag has been written to the .env file
        $isAppInstalled = MiscHelper::getEnvironmentSetting('APP_INSTALLED');

        if ($request->is('install/*'))
        {
            // Already in the installer
            // For security reasons, don't allow the installer to be used if it has been previously completed
            if (boolval($isAppInstalled))
            {
                return redirect(route('home'));
            }

            return $next($request);
        }

        if ($isAppInstalled)
        {
            // See if an update is necessary
            $this->updateDatabaseIfRequired();

            // App is configured, continue on
            return $next($request);
        }

        return redirect(route('install.check'));
    }

    private function generateAppKey()
    {
        // Generate an application key and store to the .env file
        if (!file_exists($this->environmentFilePath))
        {
            $key = MiscHelper::randomString(32);
            file_put_contents($this->environmentFilePath, sprintf('APP_KEY=%s', $key) . PHP_EOL);
            app('config')->set(['app' => ['key' => $key]]);
        }
    }

    private function updateDatabaseIfRequired()
    {
        $versionNumber = UserConfig::getOrCreateModel('app_version');
        $appVersionNumber = config('app.version');

        if (is_null($appVersionNumber) || $versionNumber->value != $appVersionNumber)
        {
            Log::info('Upgrading database', ['new_version' => $appVersionNumber]);

            Artisan::call('config:cache');
            Artisan::call('cache:clear');
            Artisan::call('view:clear');
            Artisan::call('migrate', ['--force' => true]);
            Artisan::call('db:seed', ['--force' => true]);

            // Run any data migrations relevant to the new version
            $dataMigrationsFolder = join(DIRECTORY_SEPARATOR, [dirname(dirname(dirname(__DIR__))), 'database', 'data_migrations']);
            $di = new \RecursiveDirectoryIterator($dataMigrationsFolder, \RecursiveDirectoryIterator::SKIP_DOTS);
            $updateClasses = [];

            // First load all data migration classes and examine the version number to decide if we need it or not
            /** @var \SplFileInfo $fileInfo */
            foreach ($di as $fileInfo)
            {
                if (!$di->isFile() || $di->getExtension() != 'php')
                {
                    continue;
                }

                $className = substr($fileInfo->getFilename(), 0, strlen($fileInfo->getFilename()) - 4);

                /** @var DataMigration $upgradeClass */
                $upgradeClass = new $className;
                if (version_compare($versionNumber->value, $upgradeClass->getVersion()) < 0)
                {
                    $updateClasses[] = $upgradeClass;
                }
            }

            // Now run the migrations
            foreach ($updateClasses as $upgradeClass)
            {
                $upgradeClass->run($versionNumber);
            }

            // Save the new version number
            $versionNumber->value = $appVersionNumber;
            $versionNumber->save();
        }
    }
}