package com.njcn.gather.systemops.database.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.gather.systemops.database.constant.DatabaseOpsConst;
import com.njcn.gather.systemops.database.mapper.DatabaseRestoreRecordMapper;
import com.njcn.gather.systemops.database.pojo.enums.BackupStrategyEnum;
import com.njcn.gather.systemops.database.pojo.enums.OperationTypeEnum;
import com.njcn.gather.systemops.database.pojo.enums.RestoreModeEnum;
import com.njcn.gather.systemops.database.pojo.enums.TaskStatusEnum;
import com.njcn.gather.systemops.database.pojo.param.DatabaseRestoreParam;
import com.njcn.gather.systemops.database.pojo.po.DatabaseBackupFile;
import com.njcn.gather.systemops.database.pojo.po.DatabaseConnection;
import com.njcn.gather.systemops.database.pojo.po.DatabaseOperationTask;
import com.njcn.gather.systemops.database.pojo.po.DatabaseRestoreRecord;
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTaskCreateVO;
import com.njcn.gather.systemops.database.service.DatabaseBackupFileService;
import com.njcn.gather.systemops.database.service.DatabaseConnectionService;
import com.njcn.gather.systemops.database.service.DatabaseOperationTaskService;
import com.njcn.gather.systemops.database.service.DatabaseRestoreService;
import com.njcn.gather.systemops.database.support.spi.DatabaseConnectionOperator;
import com.njcn.gather.systemops.database.support.spi.DatabaseOperatorRegistry;
import com.njcn.gather.systemops.database.support.spi.DatabaseRestoreOperator;
import com.njcn.gather.systemops.database.util.DatabaseOpsIdUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Locale;
import java.util.concurrent.ExecutorService;

/**
 * 数据库恢复服务实现。
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class DatabaseRestoreServiceImpl extends ServiceImpl<DatabaseRestoreRecordMapper, DatabaseRestoreRecord> implements DatabaseRestoreService {

    private final DatabaseConnectionService databaseConnectionService;
    private final DatabaseOperationTaskService databaseOperationTaskService;
    private final DatabaseBackupFileService databaseBackupFileService;
    private final DatabaseOperatorRegistry databaseOperatorRegistry;
    private final ObjectMapper objectMapper;
    @Resource(name = "dbmsTaskExecutorService")
    private ExecutorService dbmsTaskExecutorService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public DatabaseTaskCreateVO createRestoreTask(DatabaseRestoreParam.CreateParam param) {
        DatabaseConnection connection = databaseConnectionService.requireEnabled(param.getConnectionId());
        DatabaseBackupFile backupFile = requireBackupFile(param.getBackupFileId());
        validateRestoreParam(param, connection, backupFile);
        if (databaseOperationTaskService.existsRunningTask(connection.getId())) {
            throw new BusinessException(CommonResponseEnum.FAIL, "当前连接存在运行中的任务");
        }
        DatabaseOperationTask task = buildRestoreTask(param, connection, backupFile);
        databaseOperationTaskService.save(task);
        DatabaseRestoreRecord record = buildRestoreRecord(param, connection, backupFile, task);
        this.save(record);
        dbmsTaskExecutorService.submit(() -> executeRestoreTask(task.getId(), record.getId(), param));
        DatabaseTaskCreateVO vo = new DatabaseTaskCreateVO();
        vo.setTaskId(task.getId());
        vo.setTaskNo(task.getTaskNo());
        vo.setTaskStatus(task.getTaskStatus());
        return vo;
    }

    private void executeRestoreTask(String taskId, String recordId, DatabaseRestoreParam.CreateParam param) {
        DatabaseOperationTask task = databaseOperationTaskService.getById(taskId);
        DatabaseRestoreRecord record = this.getById(recordId);
        try {
            markRunning(task);
            DatabaseConnection connection = databaseConnectionService.requireEnabled(task.getConnectionId());
            DatabaseBackupFile backupFile = requireBackupFile(record.getBackupFileId());
            databaseBackupFileService.validateBackupFileReadable(backupFile);
            String password = databaseConnectionService.resolvePassword(connection, param.getTemporaryPassword());
            DatabaseRestoreOperator operator = databaseOperatorRegistry.getRestoreOperator(connection.getDbType(), backupFile.getBackupStrategy());
            operator.executeRestore(task, record, backupFile, connection, password, param);
            record.setResultMessage("恢复任务执行成功");
            record.setUpdateTime(LocalDateTime.now());
            this.updateById(record);
            markSuccess(task, "恢复任务执行成功");
        } catch (Exception exception) {
            log.error("数据库恢复任务失败，taskId={}", taskId, exception);
            record.setResultMessage(exception.getMessage());
            record.setUpdateTime(LocalDateTime.now());
            this.updateById(record);
            markFail(task, exception.getMessage());
        }
    }

    private void validateRestoreParam(DatabaseRestoreParam.CreateParam param, DatabaseConnection connection, DatabaseBackupFile backupFile) {
        if (!connection.getDbType().equals(backupFile.getDbType())) {
            throw new BusinessException(CommonResponseEnum.FAIL, "备份文件数据库类型和目标连接数据库类型不一致");
        }
        String restoreMode = resolveRestoreMode(param.getRestoreMode());
        if ((RestoreModeEnum.TRUNCATE.name().equals(restoreMode) || RestoreModeEnum.REPLACE.name().equals(restoreMode))
                && !DatabaseOpsConst.CONFIRM_OVERWRITE.equals(param.getOverwriteConfirmText())) {
            throw new BusinessException(CommonResponseEnum.FAIL, "覆盖类恢复必须输入确认覆盖");
        }
        databaseBackupFileService.validateBackupFileReadable(backupFile);
        String password = databaseConnectionService.resolvePassword(connection, param.getTemporaryPassword());
        DatabaseConnectionOperator operator = databaseOperatorRegistry.getConnectionOperator(connection.getDbType());
        if (!Boolean.TRUE.equals(operator.test(connection, password).getSuccess())) {
            throw new BusinessException(CommonResponseEnum.FAIL, "目标连接测试失败，不能创建恢复任务");
        }
        if (BackupStrategyEnum.DATA_PUMP.name().equals(backupFile.getBackupStrategy())) {
            if (StrUtil.isBlank(backupFile.getDirectoryName()) || StrUtil.isBlank(backupFile.getDumpFileName())) {
                throw new BusinessException(CommonResponseEnum.FAIL, "Data Pump 备份记录缺少目录或文件名");
            }
        }
        if (BackupStrategyEnum.JDBC_EXPORT.name().equals(backupFile.getBackupStrategy())
                && StrUtil.isBlank(backupFile.getMetadataFilePath())) {
            throw new BusinessException(CommonResponseEnum.FAIL, "JDBC_EXPORT 备份缺少元数据文件，不能恢复");
        }
    }

    private DatabaseOperationTask buildRestoreTask(DatabaseRestoreParam.CreateParam param, DatabaseConnection connection, DatabaseBackupFile backupFile) {
        DatabaseOperationTask task = new DatabaseOperationTask();
        task.setId(DatabaseOpsIdUtil.uuid());
        task.setTaskNo(DatabaseOpsIdUtil.taskNo("DBMSR"));
        task.setConnectionId(connection.getId());
        task.setDbType(connection.getDbType());
        task.setOperationType(OperationTypeEnum.RESTORE.name());
        task.setBackupStrategy(backupFile.getBackupStrategy());
        task.setTaskStatus(TaskStatusEnum.WAITING.name());
        task.setSchemaName(resolveTargetSchemaName(param, connection));
        task.setTargetNamesJson(backupFile.getTargetNamesJson());
        task.setRequestParamJson(writeJsonWithoutPassword(param));
        task.setProgressPercent(BigDecimal.ZERO);
        task.setState(DatabaseOpsConst.STATE_ENABLED);
        task.setCreateTime(LocalDateTime.now());
        task.setUpdateTime(LocalDateTime.now());
        return task;
    }

    private DatabaseRestoreRecord buildRestoreRecord(DatabaseRestoreParam.CreateParam param, DatabaseConnection connection,
                                                     DatabaseBackupFile backupFile, DatabaseOperationTask task) {
        String restoreMode = resolveRestoreMode(param.getRestoreMode());
        DatabaseRestoreRecord record = new DatabaseRestoreRecord();
        record.setId(DatabaseOpsIdUtil.uuid());
        record.setTaskId(task.getId());
        record.setBackupFileId(backupFile.getId());
        record.setConnectionId(connection.getId());
        record.setDbType(connection.getDbType());
        record.setRestoreMode(restoreMode);
        record.setTargetSchemaName(resolveTargetSchemaName(param, connection));
        record.setTargetNamesJson(backupFile.getTargetNamesJson());
        record.setTableExistsAction(restoreMode);
        record.setOverwriteConfirmed(DatabaseOpsConst.CONFIRM_OVERWRITE.equals(param.getOverwriteConfirmText()) ? 1 : 0);
        record.setState(DatabaseOpsConst.STATE_ENABLED);
        record.setCreateTime(LocalDateTime.now());
        record.setUpdateTime(LocalDateTime.now());
        return record;
    }

    private String resolveTargetSchemaName(DatabaseRestoreParam.CreateParam param, DatabaseConnection connection) {
        if (DatabaseOpsConst.DB_TYPE_MYSQL.equals(connection.getDbType())) {
            return StrUtil.blankToDefault(param.getTargetSchemaName(), connection.getDatabaseName());
        }
        return StrUtil.blankToDefault(param.getTargetSchemaName(), connection.getSchemaName());
    }

    private DatabaseBackupFile requireBackupFile(String backupFileId) {
        DatabaseBackupFile backupFile = databaseBackupFileService.getById(backupFileId);
        if (backupFile == null || !Integer.valueOf(DatabaseOpsConst.STATE_ENABLED).equals(backupFile.getState())) {
            throw new BusinessException(CommonResponseEnum.FAIL, "备份文件不存在或已删除");
        }
        return backupFile;
    }

    private String resolveRestoreMode(String restoreMode) {
        String value = StrUtil.blankToDefault(restoreMode, RestoreModeEnum.SKIP.name()).trim().toUpperCase(Locale.ROOT);
        try {
            return RestoreModeEnum.valueOf(value).name();
        } catch (Exception exception) {
            throw new BusinessException(CommonResponseEnum.FAIL, "不支持的恢复模式：" + restoreMode);
        }
    }

    private void markRunning(DatabaseOperationTask task) {
        task.setTaskStatus(TaskStatusEnum.RUNNING.name());
        task.setStartedAt(LocalDateTime.now());
        task.setUpdateTime(LocalDateTime.now());
        databaseOperationTaskService.updateById(task);
    }

    private void markSuccess(DatabaseOperationTask task, String message) {
        task.setTaskStatus(TaskStatusEnum.SUCCESS.name());
        task.setResultMessage(message);
        task.setProgressPercent(new BigDecimal("100.00"));
        task.setFinishedAt(LocalDateTime.now());
        task.setUpdateTime(LocalDateTime.now());
        databaseOperationTaskService.updateById(task);
    }

    private void markFail(DatabaseOperationTask task, String message) {
        task.setTaskStatus(TaskStatusEnum.FAIL.name());
        task.setResultMessage(message);
        task.setFinishedAt(LocalDateTime.now());
        task.setUpdateTime(LocalDateTime.now());
        databaseOperationTaskService.updateById(task);
    }

    private String writeJsonWithoutPassword(DatabaseRestoreParam.CreateParam param) {
        try {
            DatabaseRestoreParam.CreateParam copy = new DatabaseRestoreParam.CreateParam();
            copy.setConnectionId(param.getConnectionId());
            copy.setBackupFileId(param.getBackupFileId());
            copy.setRestoreMode(param.getRestoreMode());
            copy.setTargetSchemaName(param.getTargetSchemaName());
            copy.setOverwriteConfirmText(param.getOverwriteConfirmText());
            return objectMapper.writeValueAsString(copy);
        } catch (Exception exception) {
            throw new BusinessException(CommonResponseEnum.JSON_CONVERT_EXCEPTION, exception.getMessage());
        }
    }
}
