/**
 * Copyright (c) 2016-2020, Michael Yang 杨福海 ([email protected]).
 * <p>
 * Licensed under the GNU Lesser General Public License (LGPL) ,Version 3.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.gnu.org/licenses/lgpl-3.0.txt
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.jpress.web.install;

import com.jfinal.aop.Aop;
import com.jfinal.aop.Before;
import com.jfinal.kit.HashKit;
import com.jfinal.kit.Ret;
import com.jfinal.plugin.activerecord.ActiveRecordPlugin;
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.DbKit;
import io.jboot.db.ArpManager;
import io.jboot.db.datasource.DataSourceConfig;
import io.jboot.db.datasource.DataSourceConfigManager;
import io.jboot.utils.StrUtil;
import io.jboot.web.controller.annotation.RequestMapping;
import io.jboot.web.validate.EmptyValidate;
import io.jboot.web.validate.Form;
import io.jpress.JPressConsts;
import io.jpress.JPressOptions;
import io.jpress.core.install.DbExecuter;
import io.jpress.core.install.InstallManager;
import io.jpress.core.install.Installer;
import io.jpress.model.Role;
import io.jpress.model.User;
import io.jpress.service.OptionService;
import io.jpress.service.RoleService;
import io.jpress.service.UserService;
import io.jpress.web.base.ControllerBase;

import java.io.IOException;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;

/**
 * @author Michael Yang 杨福海 ([email protected])
 * @version V1.0
 * @Package io.jpress.web
 */
@RequestMapping("/install")
@Before(InstallInterceptor.class)
public class InstallController extends ControllerBase {

    public void index() {
        render("/WEB-INF/install/views/step1.html");
    }

    public void step1() {
        redirect("/install");
    }

    public void step2() {

        setAttr("JPRESS_DB_HOST", System.getenv("JPRESS_DB_HOST"));
        setAttr("JPRESS_DB_PORT", System.getenv("JPRESS_DB_PORT"));
        setAttr("JPRESS_DB_NAME", System.getenv("JPRESS_DB_NAME"));
        setAttr("JPRESS_DB_USER", System.getenv("JPRESS_DB_USER"));
        setAttr("JPRESS_DB_PASSWORD", System.getenv("JPRESS_DB_PASSWORD"));

        render("/WEB-INF/install/views/step2.html");
    }


    @EmptyValidate({
            @Form(name = "dbName", message = "数据库名不能为空"),
            @Form(name = "dbUser", message = "用户名不能为空"),
            @Form(name = "dbHost", message = "主机不能为空"),
            @Form(name = "dbPort", message = "端口号不能为空"),
    })
    public void gotoStep3() {

        String dbName = getPara("dbName");
        String dbUser = getPara("dbUser");
        String dbPwd = getPara("dbPwd");
        String dbHost = getPara("dbHost");
        int dbPort = getParaToInt("dbPort");


        boolean dbAutoCreate = getParaToBoolean("dbAutoCreate", false);
        if (dbAutoCreate) {
            if (!createDatabase(dbName, dbUser, dbPwd, dbHost, dbPort)) {
                renderJson(Ret.fail().set("message", "无法自动创建数据库,可能是用户名密码错误,或没有权限").set("errorCode", 5));
                return;
            }
        }


        try {

            InstallManager.me().init(dbName, dbUser, dbPwd, dbHost, dbPort);

            if (InstallManager.me().isDbExist() && !InstallManager.me().isJPressDb()) {
                renderJson(Ret.fail("message", "无法安装,该数据库已有表信息了,为了安全起见,请选择全新的数据库进行安装。")
                        .set("errorCode", 5));
                return;
            }

        } catch (Exception e) {
            e.printStackTrace();
            renderFailJson();
            return;
        }

        renderOkJson();
    }


    public void step3() {

        if (!InstallManager.me().isInited()) {
            redirect("/install");
            return;
        }

        //数据库需要升级
        if (InstallManager.me().isDbExist()
                && InstallManager.me().isJPressDb()
                && InstallManager.me().isNeedUpgrade()) {
            render("/WEB-INF/install/views/step3_upgrade.html");
        }

        //数据库已经存在
        //确定是 jpress 的数据库
        //数据库不需要升级
        else if (InstallManager.me().isDbExist()
                && InstallManager.me().isJPressDb()
                && !InstallManager.me().isNeedUpgrade()) {
            render("/WEB-INF/install/views/step3_notinit.html");
        }

        //全新的数据库
        else {
            render("/WEB-INF/install/views/step3.html");
        }

    }

    public void error() {
        render("/WEB-INF/install/views/error.html");
    }


    private boolean createDatabase(String dbName, String dbUser, String dbPwd, String dbHost, int dbPort) {

        DbExecuter dbExecuter = null;
        try {
            dbExecuter = new DbExecuter("information_schema", dbUser, dbPwd, dbHost, dbPort);
        } catch (Exception e) {
            e.printStackTrace();
        }

        //可能是该用户没有 information_schema 的权限
        if (dbExecuter == null) {
            return false;
        }


        try {
            List<String> dbs = dbExecuter.query("select SCHEMA_NAME from `SCHEMATA`");
            if (dbs != null && dbs.contains(dbName)) {
                return true;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

        try {
            dbExecuter.executeSql("CREATE DATABASE IF NOT EXISTS " + dbName + " DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;");
            return true;
        } catch (SQLException e) {
            e.printStackTrace();
        }

        return false;

    }


    public void install() {

        Ret ret;

        //数据库需要升级
        if (InstallManager.me().isDbExist()
                && InstallManager.me().isJPressDb()
                && InstallManager.me().isNeedUpgrade()) {

            ret = doProcessUpgrade();
        }

        //数据库已经存在
        //确定是 jpress 的数据库
        //数据库不需要升级
        else if (InstallManager.me().isDbExist()
                && InstallManager.me().isJPressDb()
                && !InstallManager.me().isNeedUpgrade()) {

            ret = doProcessReInstall();
        }

        //全新的数据库
        else {
            ret = doProcessInstall();
        }

        // 设置 JPress 的版本
        if (ret.isOk()) {

            OptionService optionService = Aop.get(OptionService.class);
            optionService.saveOrUpdate("jpress_version", JPressConsts.VERSION);
            optionService.saveOrUpdate("jpress_version_code", JPressConsts.VERSION_CODE);
        }


        renderJson(ret);
    }

    private Ret doProcessUpgrade() {

        String username = getPara("username");
        String pwd = getPara("pwd");
        String confirmPwd = getPara("confirmPwd");

        if (StrUtil.isNotBlank(username)) {

            if (StrUtil.isBlank(pwd)) {
                return Ret.fail().set("message", "密码不能为空").set("errorCode", 3);
            }

            if (StrUtil.isBlank(confirmPwd)) {
                return Ret.fail().set("message", "确认密码不能为空").set("errorCode", 4);
            }

            if (pwd.equals(confirmPwd) == false) {
                return Ret.fail().set("message", "两次输入密码不一致").set("errorCode", 5);
            }
        }

        try {
            InstallManager.me().doUpgradeDatabase();
            Thread.sleep(2000);
        } catch (Exception e) {
            e.printStackTrace();
            return Ret.fail().set("message", e.getMessage());
        }

        initActiveRecordPlugin();

        initFirstUser(username, pwd);

        if (doFinishedInstall()) {
            return Ret.ok();
        } else {
            return Ret.fail().set("message", "classes目录没有写入权限,请查看服务器配置是否正确。");
        }

    }

    private Ret doProcessReInstall() {

        String username = getPara("username");
        String pwd = getPara("pwd");
        String confirmPwd = getPara("confirmPwd");

        if (StrUtil.isNotBlank(username)) {

            if (StrUtil.isBlank(pwd)) {
                return Ret.fail().set("message", "密码不能为空").set("errorCode", 3);
            }

            if (StrUtil.isBlank(confirmPwd)) {
                return Ret.fail().set("message", "确认密码不能为空").set("errorCode", 4);
            }

            if (pwd.equals(confirmPwd) == false) {
                return Ret.fail().set("message", "两次输入密码不一致").set("errorCode", 5);
            }
        }


        initActiveRecordPlugin();

        initFirstUser(username, pwd);


        if (doFinishedInstall()) {
            return Ret.ok();
        } else {
            return Ret.fail().set("message", "classes目录没有写入权限,请查看服务器配置是否正确。");
        }
    }

    private Ret doProcessInstall() {

        String webName = getPara("web_name");
        String webTitle = getPara("web_title");
        String webSubtitle = getPara("web_subtitle");

        String username = getPara("username");
        String pwd = getPara("pwd");
        String confirmPwd = getPara("confirmPwd");

        if (StrUtil.isBlank(webName)) {
            return Ret.fail().set("message", "网站名称不能为空");
        }

        if (StrUtil.isBlank(webTitle)) {
            return Ret.fail().set("message", "网站标题不能为空");
        }

        if (StrUtil.isBlank(webSubtitle)) {
            return Ret.fail().set("message", "网站副标题不能为空");
        }

        if (StrUtil.isBlank(username)) {
            return Ret.fail().set("message", "账号不能为空");
        }

        if (StrUtil.isBlank(pwd)) {
            return Ret.fail().set("message", "密码不能为空");
        }

        if (StrUtil.isBlank(confirmPwd)) {
            return Ret.fail().set("message", "确认密码不能为空");
        }

        if (pwd.equals(confirmPwd) == false) {
            return Ret.fail().set("message", "两次输入密码不一致").set("errorCode", 5);
        }

        try {
            InstallManager.me().doInitDatabase();
            Thread.sleep(2000);
        } catch (Exception e) {
            e.printStackTrace();
            return Ret.fail().set("message", e.getMessage());
        }

        initActiveRecordPlugin();

        OptionService optionService = Aop.get(OptionService.class);
        optionService.saveOrUpdate("web_name", webName);
        optionService.saveOrUpdate("web_title", webTitle);
        optionService.saveOrUpdate("web_subtitle", webSubtitle);

        JPressOptions.set("web_name", webName);
        JPressOptions.set("web_title", webTitle);
        JPressOptions.set("web_subtitle", webSubtitle);

        initFirstUser(username, pwd);

        if (doFinishedInstall()) {
            return Ret.ok();
        } else {
            return Ret.fail().set("message", "classes目录没有写入权限,请查看服务器配置是否正确。");
        }

    }

    /**
     * 初始化第一个用户
     *
     * @param username
     * @param pwd
     */
    private void initFirstUser(String username, String pwd) {

        if (StrUtil.isBlank(username) || StrUtil.isBlank(pwd)) {
            return;
        }

        UserService userService = Aop.get(UserService.class);
        User user = userService.findById(1L);

        if (user == null) {
            user = new User();
            user.setNickname(username);
            user.setRealname(username);
            user.setCreateSource(User.SOURCE_WEB_REGISTER);
            user.setCreated(new Date());
            user.setActivated(new Date());
        }


        String salt = HashKit.generateSaltForSha256();
        String hashedPass = HashKit.sha256(salt + pwd);

        user.setSalt(salt);
        user.setPassword(hashedPass);

        user.setUsername(username);
        if (StrUtil.isEmail(username)) {
            user.setEmail(username.toLowerCase());
        }

        user.setStatus(User.STATUS_OK);
        userService.saveOrUpdate(user);


        RoleService roleService = Aop.get(RoleService.class);

        Role role = roleService.findById(1L);
        if (role == null) {
            role = new Role();
            role.setCreated(new Date());
        }

        role.setName("默认角色");
        role.setDescription("这个是系统自动创建的默认角色");
        role.setFlag(Role.ADMIN_FLAG);
        role.setModified(new Date());

        roleService.saveOrUpdate(role);

        Db.update("DELETE FROM `user_role_mapping` WHERE `user_id` = 1");
        Db.update("INSERT INTO `user_role_mapping` (`user_id`, `role_id`) VALUES (1, 1)");
    }


    private void initActiveRecordPlugin() {


        DataSourceConfig config = InstallManager.me().getDataSourceConfig();

        // 在只有 jboot.properties 但是没有 install.lock 的情况下
        // jboot 启动的时候会出初始化 jboot.properties 里配置的插件
        // 此时,会出现 config already exist 的异常
        if (DbKit.getConfig(DataSourceConfig.NAME_DEFAULT) == null) {
            config.setName(DataSourceConfig.NAME_DEFAULT);
        } else {
            config.setName(StrUtil.uuid());
        }

        DataSourceConfigManager.me().addConfig(config);

        ActiveRecordPlugin arPlugin = ArpManager.me().createRecordPlugin(config);
        arPlugin.start();
    }


    private boolean doFinishedInstall() {
        try {

            //创建 install.lock 安装锁定文件
            InstallUtil.createInstallLockFile();

            //创建 jboot.properties 数据库配置文件
            InstallUtil.createJbootPropertiesFile();

        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }

        /**
         *  设置安装标识
         */
        Installer.setInstalled(true);

        /**
         * 通知安装监听器
         */
        Installer.notifyAllListeners();

        return true;
    }


}