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

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.gather.systemops.database.config.DbmsProperties;
import com.njcn.gather.systemops.database.constant.DatabaseOpsConst;
import com.njcn.gather.systemops.database.mapper.DatabaseBackupFileMapper;
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.vo.DatabaseBackupFileVO;
import com.njcn.gather.systemops.database.service.DatabaseBackupFileService;
import com.njcn.gather.systemops.database.util.DatabaseChecksumUtil;
import com.njcn.gather.systemops.database.util.DatabasePathUtil;
import com.njcn.web.factory.PageFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 数据库备份文件服务实现。
 */
@Service
@RequiredArgsConstructor
public class DatabaseBackupFileServiceImpl extends ServiceImpl<DatabaseBackupFileMapper, DatabaseBackupFile> implements DatabaseBackupFileService {

    private final DbmsProperties dbmsProperties;

    @Override
    public Page<DatabaseBackupFileVO> listFiles(DatabaseBackupParam.FileQueryParam param) {
        DatabaseBackupParam.FileQueryParam query = param == null ? new DatabaseBackupParam.FileQueryParam() : param;
        LambdaQueryWrapper<DatabaseBackupFile> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(DatabaseBackupFile::getState, DatabaseOpsConst.STATE_ENABLED)
                .eq(StrUtil.isNotBlank(query.getConnectionId()), DatabaseBackupFile::getConnectionId, query.getConnectionId())
                .eq(StrUtil.isNotBlank(query.getTaskId()), DatabaseBackupFile::getTaskId, query.getTaskId())
                .eq(StrUtil.isNotBlank(query.getBackupStrategy()), DatabaseBackupFile::getBackupStrategy, query.getBackupStrategy())
                .orderByDesc(DatabaseBackupFile::getCreateTime);
        Page<DatabaseBackupFile> page = this.page(new Page<>(PageFactory.getPageNum(query), PageFactory.getPageSize(query)), wrapper);
        Page<DatabaseBackupFileVO> result = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
        result.setRecords(page.getRecords().stream().map(this::toVO).collect(Collectors.toList()));
        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteBackupFile(String backupFileId, String confirmText) {
        if (!DatabaseOpsConst.CONFIRM_DELETE.equals(confirmText)) {
            throw new BusinessException(CommonResponseEnum.FAIL, "确认文案不正确");
        }
        DatabaseBackupFile file = this.lambdaQuery()
                .eq(DatabaseBackupFile::getId, backupFileId)
                .eq(DatabaseBackupFile::getState, DatabaseOpsConst.STATE_ENABLED)
                .one();
        if (file == null) {
            throw new BusinessException(CommonResponseEnum.FAIL, "备份文件不存在或已删除");
        }
        deletePhysicalPath(file, file.getFilePath());
        deletePhysicalPath(file, file.getLogFilePath());
        deletePhysicalPath(file, file.getMetadataFilePath());
        file.setState(DatabaseOpsConst.STATE_DELETED);
        file.setUpdateTime(LocalDateTime.now());
        return this.updateById(file);
    }

    @Override
    public void validateBackupFileReadable(DatabaseBackupFile backupFile) {
        validateReadablePath(backupFile, backupFile.getFilePath(), "备份文件", false, true);
        validateReadablePath(backupFile, backupFile.getMetadataFilePath(), "备份元数据文件",
                StrUtil.isBlank(backupFile.getMetadataFilePath()), false);
        if (StrUtil.isBlank(backupFile.getChecksum())) {
            throw new BusinessException(CommonResponseEnum.FAIL, "备份文件缺少校验值");
        }
        Path checksumPath = resolveChecksumPath(backupFile);
        String actualChecksum = DatabaseChecksumUtil.sha256(checksumPath);
        if (!backupFile.getChecksum().equalsIgnoreCase(actualChecksum)) {
            throw new BusinessException(CommonResponseEnum.FAIL, "备份文件校验失败");
        }
    }

    @Override
    public Path resolveManagedPath(DatabaseBackupFile backupFile, String filePath) {
        if (StrUtil.isBlank(filePath)) {
            return null;
        }
        Path path = DatabasePathUtil.normalize(filePath);
        if (path == null) {
            return null;
        }
        Path storageRoot = DatabasePathUtil.normalize(dbmsProperties.getBackup().getStoragePath());
        if (DatabasePathUtil.isUnder(path, storageRoot)) {
            return path;
        }
        Path primaryFilePath = DatabasePathUtil.normalize(backupFile.getFilePath());
        if (primaryFilePath != null) {
            Path allowedRoot = Files.isDirectory(primaryFilePath) ? primaryFilePath : primaryFilePath.getParent();
            if (allowedRoot != null && DatabasePathUtil.isUnder(path, allowedRoot)) {
                return path;
            }
        }
        throw new BusinessException(CommonResponseEnum.FAIL, "文件路径不在允许的备份目录内");
    }

    private Path resolveChecksumPath(DatabaseBackupFile backupFile) {
        Path metadataPath = resolveManagedPath(backupFile, backupFile.getMetadataFilePath());
        if (metadataPath != null && Files.exists(metadataPath) && !Files.isDirectory(metadataPath)) {
            return metadataPath;
        }
        Path filePath = resolveManagedPath(backupFile, backupFile.getFilePath());
        if (filePath == null || !Files.exists(filePath) || Files.isDirectory(filePath)) {
            throw new BusinessException(CommonResponseEnum.FAIL, "备份校验文件不存在");
        }
        return filePath;
    }

    private void deletePhysicalPath(DatabaseBackupFile backupFile, String filePath) {
        if (StrUtil.isBlank(filePath)) {
            return;
        }
        try {
            Path path = resolveManagedPath(backupFile, filePath);
            if (path == null || !Files.exists(path)) {
                return;
            }
            if (Files.isDirectory(path)) {
                try (Stream<Path> paths = Files.walk(path)) {
                    paths.sorted(Comparator.reverseOrder()).forEach(this::deleteSinglePath);
                }
            } else {
                Files.delete(path);
            }
        } catch (BusinessException exception) {
            throw exception;
        } catch (Exception exception) {
            throw new BusinessException(CommonResponseEnum.FAIL, "删除物理文件失败：" + exception.getMessage());
        }
    }

    private void deleteSinglePath(Path path) {
        try {
            Files.deleteIfExists(path);
        } catch (Exception exception) {
            throw new BusinessException(CommonResponseEnum.FAIL, "删除物理文件失败：" + exception.getMessage());
        }
    }

    private void validateReadablePath(DatabaseBackupFile backupFile, String filePath, String fileType,
                                      boolean allowBlank, boolean allowDirectory) {
        if (StrUtil.isBlank(filePath)) {
            if (allowBlank) {
                return;
            }
            throw new BusinessException(CommonResponseEnum.FAIL, fileType + "路径不能为空");
        }
        Path path = resolveManagedPath(backupFile, filePath);
        if (path == null || !Files.exists(path)) {
            throw new BusinessException(CommonResponseEnum.FAIL, fileType + "不存在");
        }
        if (Files.isDirectory(path) && !allowDirectory) {
            throw new BusinessException(CommonResponseEnum.FAIL, fileType + "不能是目录");
        }
    }

    private DatabaseBackupFileVO toVO(DatabaseBackupFile file) {
        DatabaseBackupFileVO vo = new DatabaseBackupFileVO();
        BeanUtil.copyProperties(file, vo);
        return vo;
    }
}
