package com.jeecms.backup.databasebackup;

import com.jeecms.backup.exception.BackupException;

import java.sql.Connection;
import java.sql.Statement;
import java.util.Objects;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 本地Sql Server数据库备份还原
 *
 * @author Zhu Kaixiao
 * @version 1.0
 * @date 2019/7/31 9:20
 * @copyright 江西金磊科技发展有限公司 All rights reserved. Notice
 * 仅限于授权后使用,禁止非授权传阅以及私自用于商业目的。
 */
public class LocalSqlServerBackup extends AbstractDatabaseBackup {
	static Logger log = LoggerFactory.getLogger(LocalSqlServerBackup.class);
    private static final String DRIVER_CLASS_NAME = "com.microsoft.sqlserver.jdbc.SQLServerDriver";


    /**
     * 通过T-SQL备份
     * BACKUP DATABASE zkx_backup_test TO DISK='D:\2.bak'
     * 备份生成的bak文件在db服务器上, 而不是在当前应用服务器上
     * T-SQL备份详细资料: https://docs.microsoft.com/zh-cn/sql/t-sql/statements/backup-transact-sql?view=sql-server-2017
     *
     * @author Zhu Kaixiao
     * @date 2019/7/31 9:35
     **/
    @Override
    protected String doBackup() {
        try (Connection connection = getConnection(DRIVER_CLASS_NAME, jdbcUrl(),
                backupConfig.getUsername(), backupConfig.getPassword())) {
            String tSql = String.format("BACKUP DATABASE %s TO DISK='%s'",
                    backupConfig.getDatabaseName(), backupConfig.getDataSavePath());
            log.debug("准备执行备份T-SQL [{}]", tSql);
            Statement statement = connection.createStatement();
            statement.execute(tSql);
            log.info("数据库[{}]备份成功, 备份文件路径:[{}]", backupConfig.getDatabaseName(), backupConfig.getDataSavePath());
            return backupConfig.getDataSavePath();
        } catch (Exception e) {
            log.error("数据库[{}]备份失败!\t{}", backupConfig.getDatabaseName(), e.getMessage());
            throw dressException(e);
        }
    }

    /**
     * 通过T-SQL还原, 调用该方法将会指定数据库上的所有用户链接,并回滚事务
     * 因为在还原数据库时,需要获得对数据库的独占访问权
     * <p>
     * 目前只支持还原全量备份
     * RESTORE DATABASE参考资料: https://docs.microsoft.com/zh-cn/sql/t-sql/statements/restore-statements-transact-sql?view=sql-server-2017
     *
     * @author Zhu Kaixiao
     * @date 2019/7/31 11:18
     **/
    @Override
    protected boolean doRecovery() {
        try (Connection connection = getConnection(DRIVER_CLASS_NAME, jdbcUrl(),
                backupConfig.getUsername(), backupConfig.getPassword())) {
            try {
//          ALTER DATABASE zkx_backup_test
//          SET OFFLINE WITH ROLLBACK IMMEDIATE
//          用于断开所有用户链接,并回滚所有事务;  因为在还原数据库时,需要获得对数据库的独占访问权
                String tSql1 = "ALTER DATABASE zkx_backup_test SET OFFLINE WITH ROLLBACK IMMEDIATE";
                log.debug("尝试获取数据库独占访问权 [{}]", tSql1);
                Statement statement = connection.createStatement();
                statement.execute(tSql1);
            } catch (Exception e) {
                // 如果是数据库不存在, 忽略异常
                if (!e.getMessage().contains("数据库不存在")) {
                    throw dressException(e);
                }
            }

            String tSql2 = String.format("RESTORE DATABASE %s FROM DISK='%s'",
                    backupConfig.getDatabaseName(), backupConfig.getDataSavePath());
            log.debug("准备执行还原T-SQL [{}]", tSql2);
            Statement statement = connection.createStatement();
            statement.execute(tSql2);
            log.info("数据库[{}]还原成功, 还原文件路径:[{}]", backupConfig.getDatabaseName(), backupConfig.getDataSavePath());
            return true;
        } catch (Exception e) {
            log.error("数据库[{}]还原失败!\t{}", backupConfig.getDatabaseName(), e.getMessage());
            throw dressException(e);
        }
    }

    @Override
    protected void before() {
        super.before();
        Objects.requireNonNull(backupConfig.getDatabaseName());
    }


    private BackupException dressException(Exception e) {
        Throwable cause = e.getCause();
        cause = cause == null ? e : cause;
        String msg;
        if (cause.getMessage().toLowerCase().contains("connection timed out")) {
            msg = "数据库连接超时";
        } else {
            msg = cause.getMessage().split("ClientConnectionId:")[0];
        }
        return new BackupException(msg);
    }

    private String jdbcUrl() {
        return String.format("jdbc:sqlserver://%s:%d;database=master;", backupConfig.getHost(), backupConfig.getPort());
    }
}