package com.njcn.gather.systemops.database.support.mysql;

import cn.hutool.core.util.StrUtil;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.gather.systemops.database.component.JdbcExportComponent;
import com.njcn.gather.systemops.database.config.DbmsProperties;
import com.njcn.gather.systemops.database.mapper.DatabaseOperationTaskMapper;
import com.njcn.gather.systemops.database.pojo.enums.BackupModeEnum;
import com.njcn.gather.systemops.database.pojo.enums.BackupStrategyEnum;
import com.njcn.gather.systemops.database.pojo.enums.FileFormatEnum;
import com.njcn.gather.systemops.database.pojo.enums.TaskStatusEnum;
import com.njcn.gather.systemops.database.pojo.param.DatabaseBackupParam;
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.support.spi.DatabaseBackupOperator;
import com.njcn.gather.systemops.database.util.DatabaseChecksumUtil;
import com.njcn.gather.systemops.database.util.DatabaseFileNameUtil;
import com.njcn.gather.systemops.database.util.DatabaseOpsIdUtil;
import com.njcn.gather.systemops.database.util.DatabasePathUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.Connection;
import java.sql.DriverManager;
import java.time.LocalDateTime;
import java.util.Locale;
import java.util.stream.Stream;

/**
 * MySQL JDBC_EXPORT 大数据量备份实现。
 */
@Component
@RequiredArgsConstructor
public class MysqlJdbcExportBackupOperator implements DatabaseBackupOperator {

    private final JdbcExportComponent jdbcExportComponent;
    private final DbmsProperties dbmsProperties;
    private final DatabaseOperationTaskMapper databaseOperationTaskMapper;

    @Override
    public boolean support(String dbType, String backupStrategy) {
        return "MYSQL".equalsIgnoreCase(dbType) && BackupStrategyEnum.JDBC_EXPORT.name().equals(backupStrategy);
    }

    @Override
    public DatabaseBackupFile executeBackup(DatabaseOperationTask task, DatabaseConnection connection, String password,
                                            DatabaseBackupParam.CreateParam param) throws Exception {
        Path backupDirectory = buildManagedPath(dbmsProperties.getBackup().getStoragePath(), task.getTaskNo());
        String metadataFileName = DatabaseFileNameUtil.appendTodayWithTask("mysql_jdbc_export_metadata.json", task.getTaskNo());
        Path metadataFilePath = backupDirectory.resolve(metadataFileName).normalize();
        int fetchSize = positiveOrDefault(dbmsProperties.getBackup().getMysqlFetchSize(), 1000);
        long maxPartBytes = resolveMaxPartBytes(param);
        try (Connection jdbcConnection = DriverManager.getConnection(MysqlJdbcUrlUtil.build(connection), connection.getUsername(), password)) {
            jdbcExportComponent.exportMysqlCsvV2(jdbcConnection, connection.getDatabaseName(), task.getTaskNo(), param,
                    backupDirectory, metadataFilePath, fetchSize, maxPartBytes, () -> isTaskCancelled(task.getId()));
        } catch (Exception exception) {
            throw new BusinessException(CommonResponseEnum.FAIL, exception.getMessage() + "，导出目录：" + backupDirectory);
        }
        return buildBackupFile(task, connection, param, backupDirectory, metadataFilePath);
    }

    private boolean isTaskCancelled(String taskId) {
        DatabaseOperationTask task = databaseOperationTaskMapper.selectById(taskId);
        return task != null && TaskStatusEnum.CANCELLED.name().equals(task.getTaskStatus());
    }

    private DatabaseBackupFile buildBackupFile(DatabaseOperationTask task, DatabaseConnection connection,
                                               DatabaseBackupParam.CreateParam param, Path backupDirectory,
                                               Path metadataFilePath) throws Exception {
        if (!Files.exists(metadataFilePath)) {
            throw new BusinessException(CommonResponseEnum.FAIL, "备份元数据文件未生成");
        }
        DatabaseBackupFile file = new DatabaseBackupFile();
        file.setId(DatabaseOpsIdUtil.uuid());
        file.setTaskId(task.getId());
        file.setConnectionId(connection.getId());
        file.setDbType(connection.getDbType());
        file.setBackupStrategy(task.getBackupStrategy());
        file.setFileFormat(FileFormatEnum.CSV.name());
        file.setSchemaName(task.getSchemaName());
        file.setTargetNamesJson(task.getTargetNamesJson());
        file.setBackupMode(StrUtil.blankToDefault(param.getBackupMode(), BackupModeEnum.FULL_TABLE.name()).toUpperCase(Locale.ROOT));
        file.setBackupStartTime(param.getStartTime());
        file.setBackupEndTime(param.getEndTime());
        file.setTimeColumn(param.getTimeColumn());
        file.setDirectoryName(null);
        file.setDumpFileName(null);
        file.setLogFileName(null);
        file.setFileName(backupDirectory.getFileName().toString());
        file.setFilePath(backupDirectory.toString());
        file.setLogFilePath(null);
        file.setMetadataFilePath(metadataFilePath.toString());
        file.setFileSize(readDirectoryFileSize(backupDirectory));
        file.setChecksum(DatabaseChecksumUtil.sha256(metadataFilePath));
        file.setState(1);
        file.setCreateTime(LocalDateTime.now());
        file.setUpdateTime(LocalDateTime.now());
        return file;
    }

    private long resolveMaxPartBytes(DatabaseBackupParam.CreateParam param) {
        Integer maxFileSizeMb = param.getMaxFileSizeMb();
        if (maxFileSizeMb == null || maxFileSizeMb <= 0) {
            maxFileSizeMb = positiveOrDefault(dbmsProperties.getBackup().getDefaultMaxFileSizeMb(), 512);
        }
        return maxFileSizeMb.longValue() * 1024L * 1024L;
    }

    private int positiveOrDefault(Integer value, int defaultValue) {
        return value == null || value <= 0 ? defaultValue : value;
    }

    private Long readDirectoryFileSize(Path directory) {
        try {
            if (directory != null && Files.exists(directory) && Files.isDirectory(directory)) {
                final long[] total = new long[]{0L};
                try (Stream<Path> paths = Files.walk(directory)) {
                    paths.filter(path -> Files.exists(path) && !Files.isDirectory(path))
                            .forEach(path -> {
                                try {
                                    total[0] += Files.size(path);
                                } catch (Exception ignored) {
                                    // 忽略单个文件大小读取失败，避免影响备份记录生成。
                                }
                            });
                }
                return total[0];
            }
        } catch (Exception ignored) {
            return null;
        }
        return null;
    }

    private Path buildManagedPath(String rootPath, String directoryName) {
        Path root = DatabasePathUtil.normalize(rootPath);
        if (root == null) {
            throw new BusinessException(CommonResponseEnum.FAIL, "备份目录未配置");
        }
        return root.resolve(directoryName).normalize();
    }
}
