package com.datamate.datamanagement.application;

import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.datamate.common.auth.application.ResourceAccessService;
import com.datamate.common.domain.utils.ChunksSaver;
import com.datamate.common.setting.application.SysParamApplicationService;
import com.datamate.datamanagement.interfaces.dto.*;
import com.datamate.common.infrastructure.exception.BusinessAssert;
import com.datamate.common.infrastructure.exception.BusinessException;
import com.datamate.common.infrastructure.exception.CommonErrorCode;
import com.datamate.common.infrastructure.exception.SystemErrorCode;
import com.datamate.common.interfaces.PagedResponse;
import com.datamate.datamanagement.domain.model.dataset.Dataset;
import com.datamate.datamanagement.domain.model.dataset.DatasetFile;
import com.datamate.datamanagement.domain.model.dataset.Tag;
import com.datamate.datamanagement.infrastructure.client.CollectionTaskClient;
import com.datamate.datamanagement.infrastructure.client.dto.CollectionTaskDetailResponse;
import com.datamate.datamanagement.infrastructure.exception.DataManagementErrorCode;
import com.datamate.datamanagement.infrastructure.persistence.mapper.TagMapper;
import com.datamate.datamanagement.infrastructure.persistence.repository.DatasetFileRepository;
import com.datamate.datamanagement.infrastructure.persistence.repository.DatasetRepository;
import com.datamate.datamanagement.infrastructure.persistence.repository.dto.DatasetFileCount;
import com.datamate.datamanagement.interfaces.converter.DatasetConverter;
import com.datamate.datamanagement.interfaces.dto.*;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 数据集应用服务（对齐 DB schema，使用 UUID 字符串主键）
 */
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class DatasetApplicationService {
    private static final String DATASET_PVC_NAME = "sys.management.dataset.pvc.name";
    private static final int SIMILAR_DATASET_DEFAULT_LIMIT = 4;
    private static final int SIMILAR_DATASET_MAX_LIMIT = 50;
    private static final int SIMILAR_DATASET_CANDIDATE_FACTOR = 5;
    private static final int SIMILAR_DATASET_CANDIDATE_MAX = 100;
    private static final String DERIVED_METADATA_KEY = "derived_from_file_id";
    private final DatasetRepository datasetRepository;
    private final TagMapper tagMapper;
    private final DatasetFileRepository datasetFileRepository;
    private final CollectionTaskClient collectionTaskClient;
    private final DatasetFileApplicationService datasetFileApplicationService;
    private final SysParamApplicationService sysParamService;
    private final ResourceAccessService resourceAccessService;

    @Value("${datamate.data-management.base-path:/dataset}")
    private String datasetBasePath;

    /**
     * 创建数据集
     */
    @Transactional
    public Dataset createDataset(CreateDatasetRequest createDatasetRequest) {
        BusinessAssert.isTrue(datasetRepository.findByName(createDatasetRequest.getName()) == null, DataManagementErrorCode.DATASET_ALREADY_EXISTS);
        // 创建数据集对象
        Dataset dataset = DatasetConverter.INSTANCE.convertToDataset(createDatasetRequest);
        Dataset parentDataset = resolveParentDataset(createDatasetRequest.getParentDatasetId(), dataset.getId());
        dataset.setParentDatasetId(parentDataset == null ? null : parentDataset.getId());
        dataset.initCreateParam(datasetBasePath);
        // 处理标签
        Set<Tag> processedTags = Optional.ofNullable(createDatasetRequest.getTags())
            .filter(CollectionUtils::isNotEmpty)
            .map(this::processTagNames)
            .orElseGet(HashSet::new);
        dataset.setTags(processedTags);
        datasetRepository.save(dataset);

        //todo 需要解耦这块逻辑
        if (StringUtils.hasText(createDatasetRequest.getDataSource())) {
            // 数据源id不为空，使用异步线程进行文件扫盘落库
            processDataSourceAsync(dataset.getId(), createDatasetRequest.getDataSource());
        }
        return dataset;
    }

    public String getDatasetPvcName() {
        return sysParamService.getParamByKey(DATASET_PVC_NAME);
    }

    public Dataset updateDataset(String datasetId, UpdateDatasetRequest updateDatasetRequest) {
        Dataset dataset = datasetRepository.getById(datasetId);
        BusinessAssert.notNull(dataset, DataManagementErrorCode.DATASET_NOT_FOUND);
        resourceAccessService.assertOwnerAccess(dataset.getCreatedBy());
        
        if (StringUtils.hasText(updateDatasetRequest.getName())) {
            dataset.setName(updateDatasetRequest.getName());
        }
        if (StringUtils.hasText(updateDatasetRequest.getDescription())) {
            dataset.setDescription(updateDatasetRequest.getDescription());
        }
        if (CollectionUtils.isNotEmpty(updateDatasetRequest.getTags())) {
            dataset.setTags(processTagNames(updateDatasetRequest.getTags()));
        }
        if (Objects.nonNull(updateDatasetRequest.getStatus())) {
            dataset.setStatus(updateDatasetRequest.getStatus());
        }
        if (updateDatasetRequest.isParentDatasetIdProvided()) {
            // 保存原始的 parentDatasetId 值，用于比较是否发生了变化
            String originalParentDatasetId = dataset.getParentDatasetId();

            // 处理父数据集变更：仅当请求显式包含 parentDatasetId 时处理
            // handleParentChange 内部通过 normalizeParentId 方法将空字符串和 null 都转换为 null
            // 这样既支持设置新的父数据集，也支持清除关联
            handleParentChange(dataset, updateDatasetRequest.getParentDatasetId());

            // 检查 parentDatasetId 是否发生了变化
            if (!Objects.equals(originalParentDatasetId, dataset.getParentDatasetId())) {
                // 使用 LambdaUpdateWrapper 显式地更新 parentDatasetId 字段
                // 这样即使值为 null 也能被正确更新到数据库
                datasetRepository.update(null, new LambdaUpdateWrapper<Dataset>()
                    .eq(Dataset::getId, datasetId)
                    .set(Dataset::getParentDatasetId, dataset.getParentDatasetId()));
            }
        }
        
        if (StringUtils.hasText(updateDatasetRequest.getDataSource())) {
            // 数据源id不为空，使用异步线程进行文件扫盘落库
            processDataSourceAsync(dataset.getId(), updateDatasetRequest.getDataSource());
        }
        
        // 更新其他字段（不包括 parentDatasetId，因为它已经在上面的代码中更新了）
        datasetRepository.updateById(dataset);
        return dataset;
    }

    /**
     * 删除数据集
     */
    @Transactional
    public void deleteDataset(String datasetId) {
        Dataset dataset = datasetRepository.getById(datasetId);
        BusinessAssert.notNull(dataset, DataManagementErrorCode.DATASET_NOT_FOUND);
        resourceAccessService.assertOwnerAccess(dataset.getCreatedBy());
        long childCount = datasetRepository.countByParentId(datasetId);
        BusinessAssert.isTrue(childCount == 0, DataManagementErrorCode.DATASET_HAS_CHILDREN);
        datasetRepository.removeById(datasetId);
        ChunksSaver.deleteFolder(dataset.getPath());
    }

    /**
     * 获取数据集详情
     */
    @Transactional(readOnly = true)
    public Dataset getDataset(String datasetId) {
        Dataset dataset = datasetRepository.getById(datasetId);
        BusinessAssert.notNull(dataset, DataManagementErrorCode.DATASET_NOT_FOUND);
        resourceAccessService.assertOwnerAccess(dataset.getCreatedBy());
        List<DatasetFile> datasetFiles = datasetFileRepository.findAllVisibleByDatasetId(datasetId);
        dataset.setFiles(datasetFiles);
        applyVisibleFileCounts(Collections.singletonList(dataset));
        return dataset;
    }

    /**
     * 分页查询数据集
     */
    @Transactional(readOnly = true)
    public PagedResponse<DatasetResponse> getDatasets(DatasetPagingQuery query) {
        IPage<Dataset> page = new Page<>(query.getPage(), query.getSize());
        String ownerFilterUserId = resourceAccessService.resolveOwnerFilterUserId();
        page = datasetRepository.findByCriteria(page, query, ownerFilterUserId);
        String datasetPvcName = getDatasetPvcName();
        applyVisibleFileCounts(page.getRecords());
        List<DatasetResponse> datasetResponses = DatasetConverter.INSTANCE.convertToResponse(page.getRecords());
        datasetResponses.forEach(dataset -> dataset.setPvcName(datasetPvcName));
        return PagedResponse.of(datasetResponses, page.getCurrent(), page.getTotal(), page.getPages());
    }

    @Transactional(readOnly = true)
    public List<DatasetResponse> getSimilarDatasets(String datasetId, Integer limit) {
        BusinessAssert.isTrue(StringUtils.hasText(datasetId), CommonErrorCode.PARAM_ERROR);
        Dataset dataset = datasetRepository.getById(datasetId);
        BusinessAssert.notNull(dataset, DataManagementErrorCode.DATASET_NOT_FOUND);
        resourceAccessService.assertOwnerAccess(dataset.getCreatedBy());
        Set<String> sourceTags = normalizeTagNames(dataset.getTags());
        if (sourceTags.isEmpty()) {
            return Collections.emptyList();
        }
        int safeLimit = normalizeSimilarLimit(limit);
        int candidateLimit = Math.min(
            SIMILAR_DATASET_CANDIDATE_MAX,
            Math.max(safeLimit * SIMILAR_DATASET_CANDIDATE_FACTOR, safeLimit)
        );
        String ownerFilterUserId = resourceAccessService.resolveOwnerFilterUserId();
        List<Dataset> candidates = datasetRepository.findSimilarByTags(
            new ArrayList<>(sourceTags),
            datasetId,
            candidateLimit,
            ownerFilterUserId
        );
        if (CollectionUtils.isEmpty(candidates)) {
            return Collections.emptyList();
        }
        Map<String, Integer> scoreMap = new HashMap<>();
        for (Dataset candidate : candidates) {
            int score = countSharedTags(sourceTags, candidate.getTags());
            if (score > 0 && candidate.getId() != null) {
                scoreMap.put(candidate.getId(), score);
            }
        }
        String datasetPvcName = getDatasetPvcName();
        List<Dataset> sorted = candidates.stream()
            .filter(candidate -> candidate.getId() != null && scoreMap.containsKey(candidate.getId()))
            .sorted((left, right) -> {
                int leftScore = scoreMap.getOrDefault(left.getId(), 0);
                int rightScore = scoreMap.getOrDefault(right.getId(), 0);
                if (leftScore != rightScore) {
                    return Integer.compare(rightScore, leftScore);
                }
                return resolveUpdatedTime(right).compareTo(resolveUpdatedTime(left));
            })
            .limit(safeLimit)
            .toList();
        applyVisibleFileCounts(sorted);
        List<DatasetResponse> responses = DatasetConverter.INSTANCE.convertToResponse(sorted);
        responses.forEach(item -> item.setPvcName(datasetPvcName));
        return responses;
    }

    /**
     * 处理标签名称，创建或获取标签
     */
    private Set<Tag> processTagNames(List<String> tagNames) {
        Set<Tag> tags = new HashSet<>();
        for (String tagName : tagNames) {
            Tag tag = tagMapper.findByName(tagName);
            if (tag == null) {
                Tag newTag = new Tag(tagName, null, null, "#007bff");
                newTag.setUsageCount(0L);
                newTag.setId(UUID.randomUUID().toString());
                tagMapper.insert(newTag);
                tag = newTag;
            }
            tag.setUsageCount(tag.getUsageCount() == null ? 1L : tag.getUsageCount() + 1);
            tagMapper.updateUsageCount(tag.getId(), tag.getUsageCount());
            tags.add(tag);
        }
        return tags;
    }

    private int normalizeSimilarLimit(Integer limit) {
        if (limit == null || limit <= 0) {
            return SIMILAR_DATASET_DEFAULT_LIMIT;
        }
        return Math.min(limit, SIMILAR_DATASET_MAX_LIMIT);
    }

    private Set<String> normalizeTagNames(Collection<Tag> tags) {
        if (CollectionUtils.isEmpty(tags)) {
            return Collections.emptySet();
        }
        Set<String> normalized = new HashSet<>();
        for (Tag tag : tags) {
            if (tag == null || !StringUtils.hasText(tag.getName())) {
                continue;
            }
            normalized.add(tag.getName().trim());
        }
        return normalized;
    }

    private int countSharedTags(Set<String> sourceTags, Collection<Tag> targetTags) {
        if (sourceTags.isEmpty()) {
            return 0;
        }
        Set<String> targetTagNames = normalizeTagNames(targetTags);
        if (targetTagNames.isEmpty()) {
            return 0;
        }
        int count = 0;
        for (String tagName : targetTagNames) {
            if (sourceTags.contains(tagName)) {
                count++;
            }
        }
        return count;
    }

    private LocalDateTime resolveUpdatedTime(Dataset dataset) {
        if (dataset == null) {
            return LocalDateTime.MIN;
        }
        if (dataset.getUpdatedAt() != null) {
            return dataset.getUpdatedAt();
        }
        if (dataset.getCreatedAt() != null) {
            return dataset.getCreatedAt();
        }
        return LocalDateTime.MIN;
    }

    private Dataset resolveParentDataset(String parentDatasetId, String currentDatasetId) {
        String normalized = normalizeParentId(parentDatasetId);
        if (normalized == null) {
            return null;
        }
        BusinessAssert.isTrue(!normalized.equals(currentDatasetId), CommonErrorCode.PARAM_ERROR);
        Dataset parent = datasetRepository.getById(normalized);
        BusinessAssert.notNull(parent, DataManagementErrorCode.DATASET_NOT_FOUND);
        BusinessAssert.isTrue(parent.getParentDatasetId() == null, CommonErrorCode.PARAM_ERROR);
        return parent;
    }

    private void handleParentChange(Dataset dataset, String parentDatasetId) {
        String normalized = normalizeParentId(parentDatasetId);
        String expectedPath = buildDatasetPath(datasetBasePath, dataset.getId());
        if (Objects.equals(dataset.getParentDatasetId(), normalized)
            && Objects.equals(dataset.getPath(), expectedPath)) {
            return;
        }
        long childCount = datasetRepository.countByParentId(dataset.getId());
        if (childCount > 0 && normalized != null) {
            throw BusinessException.of(DataManagementErrorCode.DATASET_HAS_CHILDREN);
        }
        Dataset parent = normalized == null ? null : resolveParentDataset(normalized, dataset.getId());
        moveDatasetPath(dataset, expectedPath);
        dataset.setParentDatasetId(parent == null ? null : parent.getId());
    }

    private String normalizeParentId(String parentDatasetId) {
        if (!StringUtils.hasText(parentDatasetId)) {
            return null;
        }
        return parentDatasetId.trim();
    }

    private String buildDatasetPath(String basePath, String datasetId) {
        String normalized = basePath;
        while (normalized.endsWith(File.separator)) {
            normalized = normalized.substring(0, normalized.length() - 1);
        }
        return normalized + File.separator + datasetId;
    }

    private void moveDatasetPath(Dataset dataset, String newPath) {
        String oldPath = dataset.getPath();
        if (Objects.equals(oldPath, newPath)) {
            return;
        }
        Path sourcePath = Paths.get(oldPath);
        Path targetPath = Paths.get(newPath);
        try {
            if (Files.exists(sourcePath)) {
                if (Files.exists(targetPath)) {
                    throw BusinessException.of(CommonErrorCode.PARAM_ERROR);
                }
                Files.createDirectories(targetPath.getParent());
                Files.move(sourcePath, targetPath);
            } else {
                Files.createDirectories(targetPath);
            }
        } catch (IOException e) {
            log.error("move dataset path error, from {} to {}", oldPath, newPath, e);
            throw BusinessException.of(SystemErrorCode.FILE_SYSTEM_ERROR);
        }
        datasetFileRepository.updateFilePathPrefix(dataset.getId(), oldPath, newPath);
        dataset.setPath(newPath);
    }

    private void applyVisibleFileCounts(List<Dataset> datasets) {
        if (CollectionUtils.isEmpty(datasets)) {
            return;
        }
        List<String> datasetIds = datasets.stream()
            .filter(Objects::nonNull)
            .map(Dataset::getId)
            .filter(StringUtils::hasText)
            .toList();
        if (datasetIds.isEmpty()) {
            return;
        }
        Map<String, Long> countMap = datasetFileRepository.countNonDerivedByDatasetIds(datasetIds).stream()
            .filter(Objects::nonNull)
            .collect(Collectors.toMap(
                DatasetFileCount::getDatasetId,
                count -> Optional.ofNullable(count.getFileCount()).orElse(0L),
                (left, right) -> left
            ));
        for (Dataset dataset : datasets) {
            if (dataset == null || !StringUtils.hasText(dataset.getId())) {
                continue;
            }
            Long visibleCount = countMap.get(dataset.getId());
            dataset.setFileCount(visibleCount != null ? visibleCount : 0L);
        }
    }

    private List<DatasetFile> filterVisibleFiles(List<DatasetFile> files) {
        if (CollectionUtils.isEmpty(files)) {
            return Collections.emptyList();
        }
        return files.stream()
            .filter(file -> !isDerivedFile(file))
            .collect(Collectors.toList());
    }

    private boolean isDerivedFile(DatasetFile datasetFile) {
        if (datasetFile == null) {
            return false;
        }
        String metadata = datasetFile.getMetadata();
        if (!StringUtils.hasText(metadata)) {
            return false;
        }
        try {
            ObjectMapper mapper = new ObjectMapper();
            Map<String, Object> metadataMap = mapper.readValue(metadata, new TypeReference<Map<String, Object>>() {});
            return metadataMap.get(DERIVED_METADATA_KEY) != null;
        } catch (Exception e) {
            log.debug("Failed to parse dataset file metadata for derived detection: {}", datasetFile.getId(), e);
            return false;
        }
    }

    /**
     * 获取数据集统计信息
     */
    @Transactional(readOnly = true)
    public Map<String, Object> getDatasetStatistics(String datasetId) {
        Dataset dataset = datasetRepository.getById(datasetId);
        if (dataset == null) {
            throw new IllegalArgumentException("Dataset not found: " + datasetId);
        }
        resourceAccessService.assertOwnerAccess(dataset.getCreatedBy());

        Map<String, Object> statistics = new HashMap<>();

        List<DatasetFile> allFiles = datasetFileRepository.findAllVisibleByDatasetId(datasetId);
        List<DatasetFile> visibleFiles = filterVisibleFiles(allFiles);
        long totalFiles = visibleFiles.size();
        long completedFiles = visibleFiles.stream()
            .filter(file -> "COMPLETED".equalsIgnoreCase(file.getStatus()))
            .count();
        Long totalSize = datasetFileRepository.sumSizeByDatasetId(datasetId);

        statistics.put("totalFiles", (int) totalFiles);
        statistics.put("completedFiles", (int) completedFiles);
        statistics.put("totalSize", totalSize != null ? totalSize : 0L);

        // 完成率计算
        float completionRate = 0.0f;
        if (totalFiles > 0) {
            completionRate = ((float) completedFiles) / (float) totalFiles * 100.0f;
        }
        statistics.put("completionRate", completionRate);

        // 文件类型分布统计
        Map<String, Integer> fileTypeDistribution = new HashMap<>();
        if (!visibleFiles.isEmpty()) {
            for (DatasetFile file : visibleFiles) {
                String fileType = file.getFileType() != null ? file.getFileType() : "unknown";
                fileTypeDistribution.put(fileType, fileTypeDistribution.getOrDefault(fileType, 0) + 1);
            }
        }
        statistics.put("fileTypeDistribution", fileTypeDistribution);

        // 状态分布统计
        Map<String, Integer> statusDistribution = new HashMap<>();
        if (!visibleFiles.isEmpty()) {
            for (DatasetFile file : visibleFiles) {
                String status = file.getStatus() != null ? file.getStatus() : "unknown";
                statusDistribution.put(status, statusDistribution.getOrDefault(status, 0) + 1);
            }
        }
        statistics.put("statusDistribution", statusDistribution);

        return statistics;
    }

    /**
     * 获取所有数据集的汇总统计信息
     */
    public AllDatasetStatisticsResponse getAllDatasetStatistics() {
        if (resourceAccessService.isAdmin()) {
            return datasetRepository.getAllDatasetStatistics();
        }
        String currentUserId = resourceAccessService.requireCurrentUserId();
        return datasetRepository.getAllDatasetStatisticsByCreatedBy(currentUserId);
    }

    /**
     * 异步处理数据源文件扫描
     *
     * @param datasetId    数据集ID
     * @param dataSourceId 数据源ID（归集任务ID）
     */
    @Async
    public void processDataSourceAsync(String datasetId, String dataSourceId) {
        try {
            log.info("Initiating data source file scanning, dataset ID: {}, collection task ID: {}", datasetId, dataSourceId);
            CollectionTaskDetailResponse taskDetail = collectionTaskClient.getTaskDetail(dataSourceId).getData();
            if (taskDetail == null) {
                log.warn("Fail to get collection task detail, task ID: {}", dataSourceId);
                return;
            }
            Path targetPath = Paths.get(taskDetail.getTargetPath());
            if (!Files.exists(targetPath) || !Files.isDirectory(targetPath)) {
                log.warn("Target path not exists or is not a directory: {}", taskDetail.getTargetPath());
                return;
            }
            List<String> filePaths = scanFilePaths(targetPath);
            if (CollectionUtils.isEmpty(filePaths)) {
                return;
            }
            datasetFileApplicationService.copyFilesToDatasetDirWithSourceRoot(datasetId, targetPath, filePaths);
            log.info("Success file scan, total files: {}", filePaths.size());
        } catch (Exception e) {
            log.error("处理数据源文件扫描失败，数据集ID: {}, 数据源ID: {}", datasetId, dataSourceId, e);
        }
    }

    private List<String> scanFilePaths(Path targetPath) {
        try (Stream<Path> paths = Files.walk(targetPath)) {
            return paths
                .filter(Files::isRegularFile)
                .map(Path::toString)
                .collect(Collectors.toList());
        } catch (IOException e) {
            log.error("Fail to scan directory: {}", targetPath, e);
            return Collections.emptyList();
        }
    }
}
