package com.fs.file.service.impl;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fs.common.config.LoginContextManager;
import com.fs.common.exception.ServiceException;
import com.fs.file.domain.SysUploadFile;
import com.fs.file.dto.FileUploadDTO;
import com.fs.file.mapper.SysFileMapper;
import com.fs.file.service.ISysFileService;
import com.fs.file.vo.FileUploadVO;
import com.fs.system.oss.OSSFactory;
import com.fs.system.oss.CloudStorageService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

/**
 * 文件服务实现
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SysFileServiceImpl extends ServiceImpl<SysFileMapper, SysUploadFile>
        implements ISysFileService {

    private final SysFileMapper sysFileMapper;

    /** 允许上传的文件类型 */
    private static final String[] ALLOWED_EXTENSIONS = {
        "jpg", "jpeg", "png", "gif", "bmp", "webp",  // 图片
        "pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx",  // 文档
        "txt", "csv", "zip", "rar", "7z"  // 其他
    };

    /** 最大文件大小（50MB） */
    private static final long MAX_FILE_SIZE = 50 * 1024 * 1024;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public FileUploadVO upload(FileUploadDTO dto) {
        MultipartFile file = dto.getFile();

        //  文件校验
        validateFile(file);

        try {
            byte[] fileBytes = file.getBytes();
            String originalFilename = file.getOriginalFilename();
            String fileExt = FileUtil.extName(originalFilename);
            String contentType = file.getContentType();
            long fileSize = file.getSize();

            // 计算MD5
            String fileMd5 = DigestUtil.md5Hex(fileBytes);

            // 秒传检查
            Long tenantId = getTenantId();
            SysUploadFile existFile = sysFileMapper.selectByMd5(tenantId, fileMd5);
            if (existFile != null) {
                log.info("文件秒传命中, md5: {}, fileId: {}", fileMd5, existFile.getId());
                // 复制一条新记录，关联新的业务
                return copyFileRecord(existFile, dto);
            }

            // 上传到云存储
            CloudStorageService storageService = OSSFactory.build();
            String prefix = StrUtil.blankToDefault(dto.getPrefix(), dto.getBizType());
            String filePath = storageService.getPath(prefix, "." + fileExt);
            String fileUrl = storageService.upload(fileBytes, filePath);

            // 保存文件记录
            SysUploadFile sysUploadFile = buildSysFile(dto, originalFilename, filePath, fileUrl,
                    fileSize, fileExt, contentType, fileMd5, storageService.getService());
            save(sysUploadFile);

            log.info("文件上传成功, fileId: {}, fileName: {}", sysUploadFile.getId(), originalFilename);

            return buildUploadVO(sysUploadFile);

        } catch (IOException e) {
            log.error("文件上传失败", e);
            throw new ServiceException("文件上传失败：" + e.getMessage());
        }
    }

    @Override
    public FileUploadVO upload(MultipartFile file, String bizType) {
        FileUploadDTO dto = new FileUploadDTO();
        dto.setFile(file);
        dto.setBizType(bizType);
        return upload(dto);
    }

    @Override
    public FileUploadVO upload(MultipartFile file, String bizType, String bizId) {
        FileUploadDTO dto = new FileUploadDTO();
        dto.setFile(file);
        dto.setBizType(bizType);
        dto.setBizId(bizId);
        return upload(dto);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<FileUploadVO> uploadBatch(List<MultipartFile> files, String bizType, String bizId) {
        List<FileUploadVO> result = new ArrayList<>();
        int sortOrder = 0;
        for (MultipartFile file : files) {
            FileUploadDTO dto = new FileUploadDTO();
            dto.setFile(file);
            dto.setBizType(bizType);
            dto.setBizId(bizId);
            dto.setSortOrder(sortOrder++);
            result.add(upload(dto));
        }
        return result;
    }

    @Override
    public boolean bindBizId(Long fileId, String bizId) {
        return update(new LambdaUpdateWrapper<SysUploadFile>()
                .eq(SysUploadFile::getId, fileId)
                .eq(SysUploadFile::getTenantId, getTenantId())
                .set(SysUploadFile::getBizId, bizId));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean bindBizIdBatch(List<Long> fileIds, String bizId) {
        if (fileIds == null || fileIds.isEmpty()) {
            return true;
        }
        return update(new LambdaUpdateWrapper<SysUploadFile>()
                .in(SysUploadFile::getId, fileIds)
                .eq(SysUploadFile::getTenantId, getTenantId())
                .set(SysUploadFile::getBizId, bizId));
    }

    @Override
    public List<SysUploadFile> listByBiz(String bizType, String bizId) {
        return sysFileMapper.selectByBiz(getTenantId(), bizType, bizId);
    }

    @Override
    public SysUploadFile getById(Long id) {
        return getOne(new LambdaQueryWrapper<SysUploadFile>()
                .eq(SysUploadFile::getId, id)
                .eq(SysUploadFile::getTenantId, getTenantId()));
    }

    @Override
    public boolean deleteById(Long id) {
        return remove(new LambdaQueryWrapper<SysUploadFile>()
                .eq(SysUploadFile::getId, id)
                .eq(SysUploadFile::getTenantId, getTenantId()));
    }

    @Override
    public boolean deleteByIds(List<Long> ids) {
        if (ids == null || ids.isEmpty()) {
            return true;
        }
        return remove(new LambdaQueryWrapper<SysUploadFile>()
                .in(SysUploadFile::getId, ids)
                .eq(SysUploadFile::getTenantId, getTenantId()));
    }

    @Override
    public boolean deleteByBiz(String bizType, String bizId) {
        return remove(new LambdaQueryWrapper<SysUploadFile>()
                .eq(SysUploadFile::getTenantId, getTenantId())
                .eq(SysUploadFile::getBizType, bizType)
                .eq(SysUploadFile::getBizId, bizId));
    }

    @Override
    public FileUploadVO checkFastUpload(String md5, String bizType, String bizId) {
        SysUploadFile existFile = sysFileMapper.selectByMd5(getTenantId(), md5);
        if (existFile == null) {
            return null;
        }
        // 复制记录并返回
        FileUploadDTO dto = new FileUploadDTO();
        dto.setBizType(bizType);
        dto.setBizId(bizId);
        return copyFileRecord(existFile, dto);
    }

    // ==================== 私有方法 ====================

    /**
     * 文件校验
     */
    private void validateFile(MultipartFile file) {
        if (file == null || file.isEmpty()) {
            throw new ServiceException("上传文件不能为空");
        }

        // 校验文件大小
        if (file.getSize() > MAX_FILE_SIZE) {
            throw new ServiceException("文件大小不能超过 " + (MAX_FILE_SIZE / 1024 / 1024) + "MB");
        }

        // 校验文件类型
        String fileExt = FileUtil.extName(file.getOriginalFilename());
        if (!isAllowedExtension(fileExt)) {
            throw new ServiceException("不支持的文件类型：" + fileExt);
        }
    }

    /**
     * 检查文件扩展名是否允许
     */
    private boolean isAllowedExtension(String ext) {
        if (StrUtil.isBlank(ext)) {
            return false;
        }
        for (String allowedExt : ALLOWED_EXTENSIONS) {
            if (allowedExt.equalsIgnoreCase(ext)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 构建文件实体
     */
    private SysUploadFile buildSysFile(FileUploadDTO dto, String originalFilename, String filePath,
                                       String fileUrl, long fileSize, String fileExt,
                                       String contentType, String fileMd5, int storageType) {
        SysUploadFile sysUploadFile = new SysUploadFile();
        sysUploadFile.setTenantId(getTenantId());
        sysUploadFile.setBizType(dto.getBizType());
        sysUploadFile.setBizId(dto.getBizId());
        sysUploadFile.setFileName(originalFilename);
        sysUploadFile.setFilePath(filePath);
        sysUploadFile.setFileUrl(fileUrl);
        sysUploadFile.setFileSize(fileSize);
        sysUploadFile.setFileExt(fileExt);
        sysUploadFile.setFileType(contentType);
        sysUploadFile.setStorageType(storageType);
        sysUploadFile.setFileMd5(fileMd5);
        sysUploadFile.setSortOrder(dto.getSortOrder() != null ? dto.getSortOrder() : 0);
        sysUploadFile.setRemark(dto.getRemark());
        sysUploadFile.setStatus(1);
        sysUploadFile.setCreateTime(LocalDateTime.now());
        sysUploadFile.setUpdateTime(LocalDateTime.now());
        return sysUploadFile;
    }

    /**
     * 构建返回VO
     */
    private FileUploadVO buildUploadVO(SysUploadFile sysUploadFile) {
        return FileUploadVO.builder()
                .fileId(sysUploadFile.getId())
                .fileName(sysUploadFile.getFileName())
                .fileUrl(sysUploadFile.getFileUrl())
                .fileSize(sysUploadFile.getFileSize())
                .fileSizeFormat(formatFileSize(sysUploadFile.getFileSize()))
                .fileExt(sysUploadFile.getFileExt())
                .fileType(sysUploadFile.getFileType())
                .fileMd5(sysUploadFile.getFileMd5())
                .build();
    }

    /**
     * 复制文件记录（秒传场景）
     */
    private FileUploadVO copyFileRecord(SysUploadFile existFile, FileUploadDTO dto) {
        SysUploadFile newFile = new SysUploadFile();
        newFile.setTenantId(getTenantId());
        newFile.setBizType(dto.getBizType());
        newFile.setBizId(dto.getBizId());
        newFile.setFileName(existFile.getFileName());
        newFile.setFilePath(existFile.getFilePath());
        newFile.setFileUrl(existFile.getFileUrl());
        newFile.setFileSize(existFile.getFileSize());
        newFile.setFileExt(existFile.getFileExt());
        newFile.setFileType(existFile.getFileType());
        newFile.setStorageType(existFile.getStorageType());
        newFile.setBucketName(existFile.getBucketName());
        newFile.setFileMd5(existFile.getFileMd5());
        newFile.setSortOrder(dto.getSortOrder() != null ? dto.getSortOrder() : 0);
        newFile.setRemark(dto.getRemark());
        newFile.setStatus(1);
        newFile.setCreateTime(LocalDateTime.now());
        newFile.setUpdateTime(LocalDateTime.now());
        save(newFile);

        return buildUploadVO(newFile);
    }

    /**
     * 格式化文件大小
     */
    private String formatFileSize(Long size) {
        if (size == null || size <= 0) {
            return "0B";
        }
        String[] units = {"B", "KB", "MB", "GB", "TB"};
        int unitIndex = 0;
        double fileSize = size;
        while (fileSize >= 1024 && unitIndex < units.length - 1) {
            fileSize /= 1024;
            unitIndex++;
        }
        return String.format("%.2f%s", fileSize, units[unitIndex]);
    }

    /**
     * 获取当前租户ID
     */
    private Long getTenantId() {
        try {
            return LoginContextManager.getCurrentCompanyId();
        } catch (Exception e) {
            return 0L; // 默认租户
        }
    }
}
