<?php

/**
 * Copyright Maarch since 2008 under licence GPLv3.
 * See LICENCE.txt file at the root folder for more details.
 * This file is part of Maarch software.
 *
 */

/**
 * @brief Version Update Controller
 * @author dev@maarch.org
 */

namespace VersionUpdate\controllers;

use Docserver\models\DocserverModel;
use Gitlab\Client;
use Group\controllers\PrivilegeController;
use Parameter\models\ParameterModel;
use Slim\Http\Request;
use Slim\Http\Response;
use SrcCore\models\CoreConfigModel;
use SrcCore\models\DatabaseModel;
use SrcCore\models\DatabasePDO;
use SrcCore\models\ValidatorModel;

class VersionUpdateController
{
    public function get(Request $request, Response $response)
    {
        if (!PrivilegeController::hasPrivilege(['privilegeId' => 'admin_update_control', 'userId' => $GLOBALS['id']])) {
            return $response->withStatus(403)->withJson(['errors' => 'Service forbidden']);
        }

        $client = Client::create('https://labs.maarch.org/api/v4/');
        try {
            $tags = $client->api('tags')->all('12');
        } catch (\Exception $e) {
            return $response->withJson(['errors' => $e->getMessage()]);
        }

        $applicationVersion = CoreConfigModel::getApplicationVersion();
        if (empty($applicationVersion)) {
            return $response->withStatus(400)->withJson(['errors' => "Can't load package.json"]);
        }

        $currentVersion = $applicationVersion;

        $versions = explode('.', $currentVersion);
        $currentVersionBranch = "{$versions[0]}.{$versions[1]}";
        $currentVersionTag = $versions[2];
        $currentVersionBranchYear = $versions[0];
        $currentVersionBranchMonth = $versions[1];

        $availableMinorVersions = [];
        $availableMajorVersions = [];

        foreach ($tags as $value) {
            if (!preg_match("/^\d{2}\.\d{2}\.\d+$/", $value['name'])) {
                continue;
            }
            $explodedValue = explode('.', $value['name']);
            $tag = $explodedValue[2];

            $pos = strpos($value['name'], $currentVersionBranch);
            if ($pos === false) {
                $year = substr($value['name'], 0, 2);
                $month = substr($value['name'], 3, 2);
                if (($year == $currentVersionBranchYear && $month > $currentVersionBranchMonth) || $year > $currentVersionBranchYear) {
                    $availableMajorVersions[] = $value['name'];
                }
            } else {
                if ($tag > $currentVersionTag) {
                    $availableMinorVersions[] = $value['name'];
                }
            }
        }

        natcasesort($availableMinorVersions);
        natcasesort($availableMajorVersions);

        if (empty($availableMinorVersions)) {
            $lastAvailableMinorVersion = null;
        } else {
            $lastAvailableMinorVersion = $availableMinorVersions[0];
        }

        if (empty($availableMajorVersions)) {
            $lastAvailableMajorVersion = null;
        } else {
            $lastAvailableMajorVersion = $availableMajorVersions[0];
        }

        $output = [];

        exec('git status --porcelain --untracked-files=no 2>&1', $output);

        $multiCustom = false;
        if (is_file('custom/custom.json')) {
            $jsonFile = file_get_contents('custom/custom.json');
            $jsonFile = json_decode($jsonFile, true);
            $multiCustom = count($jsonFile) > 1;
        }

        return $response->withJson([
            'lastAvailableMinorVersion' => $lastAvailableMinorVersion,
            'lastAvailableMajorVersion' => $lastAvailableMajorVersion,
            'currentVersion'            => $currentVersion,
            'canUpdate'                 => empty($output),
            'diffOutput'                => $output,
            'multiCustom'               => $multiCustom
        ]);
    }

    /**
        * @codeCoverageIgnore
    */
    public function update(Request $request, Response $response)
    {
        if (!PrivilegeController::hasPrivilege(['privilegeId' => 'admin_update_control', 'userId' => $GLOBALS['id']])) {
            return $response->withStatus(403)->withJson(['errors' => 'Service forbidden']);
        }

        $client = Client::create('https://labs.maarch.org/api/v4/');
        try {
            $tags = $client->api('tags')->all('12');
        } catch (\Exception $e) {
            return $response->withJson(['errors' => $e->getMessage()]);
        }

        $applicationVersion = CoreConfigModel::getApplicationVersion();
        if (empty($applicationVersion)) {
            return $response->withStatus(400)->withJson(['errors' => "Can't load package.json"]);
        }

        $currentVersion = $applicationVersion;

        $versions = explode('.', $currentVersion);
        $currentVersionBranch = "{$versions[0]}.{$versions[1]}";
        $currentVersionTag = $versions[2];

        $availableMinorVersions = [];

        foreach ($tags as $value) {
            if (strpos($value['name'], $currentVersionBranch) === false) {
                continue;
            }
            $explodedValue = explode('.', $value['name']);
            $tag = $explodedValue[2];

            if ($tag > $currentVersionTag) {
                $availableMinorVersions[] = $value['name'];
            }
        }

        if (empty($availableMinorVersions)) {
            return $response->withStatus(400)->withJson(['errors' => 'No minor versions available']);
        }

        natcasesort($availableMinorVersions);

        $minorVersion = $availableMinorVersions[0];

        $output = [];
        exec('git status --porcelain --untracked-files=no 2>&1', $output);
        if (!empty($output)) {
            return $response->withStatus(400)->withJson(['errors' => 'Some files are modified. Can not update application', 'lang' => 'canNotUpdateApplication']);
        }

        $minorVersions = explode('.', $minorVersion);
        $currentVersionTag = (int)$currentVersionTag;
        $currentVersionTag++;
        $sqlFiles = [];
        while ($currentVersionTag <= (int)$minorVersions[2]) {
            if (is_file("migration/{$versions[0]}.{$versions[1]}/{$versions[0]}{$versions[1]}{$currentVersionTag}.sql")) {
                if (!is_readable("migration/{$versions[0]}.{$versions[1]}/{$versions[0]}{$versions[1]}{$currentVersionTag}.sql")) {
                    return $response->withStatus(400)->withJson(['errors' => "File migration/{$versions[0]}.{$versions[1]}/{$versions[0]}{$versions[1]}{$currentVersionTag}.sql is not readable"]);
                }
                $sqlFiles[] = "migration/{$versions[0]}.{$versions[1]}/{$versions[0]}{$versions[1]}{$currentVersionTag}.sql";
            }
            $currentVersionTag++;
        }

        $control = VersionUpdateController::executeSQLUpdate(['sqlFiles' => $sqlFiles]);
        if (!empty($control['errors'])) {
            return $response->withStatus(400)->withJson(['errors' => $control['errors']]);
        }

        $currentCustomId = CoreConfigModel::getCustomId();
        if (is_file('custom/custom.json')) {
            $jsonFile = file_get_contents('custom/custom.json');
            $jsonFile = json_decode($jsonFile, true);

            foreach ($jsonFile as $custom) {
                if ($custom['id'] != $currentCustomId) {
                    DatabasePDO::reset();
                    new DatabasePDO(['customId' => $custom['id']]);

                    $controlCustom = VersionUpdateController::executeSQLUpdate(['sqlFiles' => $sqlFiles]);
                    if (!empty($controlCustom['errors'])) {
                        return $response->withStatus(400)->withJson(['errors' => "Error with custom {$custom['id']} : " . $controlCustom['errors']]);
                    }
                }
            }
        }

        $output = [];
        exec('git fetch');
        exec("git checkout {$minorVersion} 2>&1", $output, $returnCode);

        $log = "Application update from {$currentVersion} to {$minorVersion}\nCheckout response {$returnCode} => " . implode(' ', $output) . "\n";
        file_put_contents("{$control['directoryPath']}/updateVersion.log", $log, FILE_APPEND);

        if ($returnCode != 0) {
            return $response->withStatus(400)->withJson(['errors' => "Application update failed. Please check updateVersion.log at {$control['directoryPath']}"]);
        }

        return $response->withStatus(204);
    }

    private static function executeSQLUpdate(array $args)
    {
        ValidatorModel::arrayType($args, ['sqlFiles']);

        $docserver = DocserverModel::getCurrentDocserver(['typeId' => 'DOC', 'collId' => 'letterbox_coll', 'select' => ['path_template']]);
        $directoryPath = explode('/', rtrim($docserver['path_template'], '/'));
        array_pop($directoryPath);
        $directoryPath = implode('/', $directoryPath);

        if (!is_dir($directoryPath . '/migration')) {
            if (!is_writable($directoryPath)) {
                return ['errors' => 'Directory path is not writable : ' . $directoryPath];
            }
            mkdir($directoryPath . '/migration', 0755, true);
        } elseif (!is_writable($directoryPath . '/migration')) {
            return ['errors' => 'Directory path is not writable : ' . $directoryPath . '/migration'];
        }

        if (!empty($args['sqlFiles'])) {
            $config = CoreConfigModel::getJsonLoaded(['path' => 'apps/maarch_entreprise/xml/config.json']);

            $actualTime = date("dmY-His");
            $tablesToSave = '';
            foreach ($args['sqlFiles'] as $sqlFile) {
                $fileContent = file_get_contents($sqlFile);
                $explodedFile = explode("\n", $fileContent);
                foreach ($explodedFile as $key => $line) {
                    if (strpos($line, '--DATABASE_BACKUP') !== false) {
                        $lineNb = $key;
                    }
                }
                if (isset($lineNb)) {
                    $explodedLine = explode('|', $explodedFile[$lineNb]);
                    array_shift($explodedLine);
                    foreach ($explodedLine as $table) {
                        if (!empty($table)) {
                            $tablesToSave .= ' -t ' . trim($table);
                        }
                    }
                }
            }

            $execReturn = exec("pg_dump --dbname=\"postgresql://{$config['database'][0]['user']}:{$config['database'][0]['password']}@{$config['database'][0]['server']}:{$config['database'][0]['port']}/{$config['database'][0]['name']}\" {$tablesToSave} -a > \"{$directoryPath}/migration/backupDB_maarchcourrier_{$actualTime}.sql\"", $output, $intReturn);
            if (!empty($execReturn)) {
                return ['errors' => 'Pg dump failed : ' . $execReturn];
            }

            foreach ($args['sqlFiles'] as $sqlFile) {
                $fileContent = file_get_contents($sqlFile);
                DatabaseModel::exec($fileContent);
            }
        }

        return ['directoryPath' => "{$directoryPath}/migration"];
    }

    public static function executeSQLAtConnection()
    {
        $parameter = ParameterModel::getById(['select' => ['param_value_string'], 'id' => 'database_version']);

        $parameter = explode('.', $parameter['param_value_string']);
        $minorVersion = count($parameter) > 2 ? (int)$parameter[2] : 1;

        $applicationVersion = CoreConfigModel::getApplicationVersion();
        $versions = explode('.', $applicationVersion);
        $currentVersion = (int)$versions[2];

        $minorVersion++;
        $sqlFiles = [];
        while ($minorVersion <= $currentVersion) {
            if (is_file("migration/{$versions[0]}.{$versions[1]}/{$versions[0]}{$versions[1]}{$minorVersion}.sql")) {
                if (!is_readable("migration/{$versions[0]}.{$versions[1]}/{$versions[0]}{$versions[1]}{$minorVersion}.sql")) {
                    return ['errors' => "File migration/{$versions[0]}.{$versions[1]}/{$versions[0]}{$versions[1]}{$minorVersion}.sql is not readable"];
                }
                $sqlFiles[] = "migration/{$versions[0]}.{$versions[1]}/{$versions[0]}{$versions[1]}{$minorVersion}.sql";
            }
            $minorVersion++;
        }

        if (!empty($sqlFiles)) {
            $control = VersionUpdateController::executeSQLUpdate(['sqlFiles' => $sqlFiles]);
            if (!empty($control['errors'])) {
                return ['errors' => $control['errors']];
            }
        }

        return true;
    }
}