package org.hzhub.system.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.hzhub.common.core.constant.CacheNames;
import org.hzhub.common.core.domain.dto.OssDTO;
import org.hzhub.common.core.exception.ServiceException;
import org.hzhub.common.core.service.ConfigService;
import org.hzhub.common.core.service.OssService;
import org.hzhub.common.core.utils.MapstructUtils;
import org.hzhub.common.core.utils.SpringUtils;
import org.hzhub.common.core.utils.StreamUtils;
import org.hzhub.common.core.utils.StringUtils;
import org.hzhub.common.core.utils.file.ContentTypeUtil;
import org.hzhub.common.core.utils.file.FileUtils;
import org.hzhub.common.json.utils.JsonUtils;
import org.hzhub.common.mybatis.core.page.PageQuery;
import org.hzhub.common.mybatis.core.page.TableDataInfo;
import org.hzhub.common.oss.constant.OssConstant;
import org.hzhub.common.oss.core.OssClient;
import org.hzhub.common.oss.entity.UploadResult;
import org.hzhub.common.oss.enums.AccessPolicyType;
import org.hzhub.common.oss.factory.OssFactory;
import org.hzhub.system.domain.SysOss;
import org.hzhub.system.domain.SysOssExt;
import org.hzhub.system.domain.bo.SysOssBo;
import org.hzhub.system.domain.vo.SysOssUploadVo;
import org.hzhub.system.domain.vo.SysOssVo;
import org.hzhub.system.mapper.SysOssMapper;
import org.hzhub.system.service.ISysOssService;
import org.jetbrains.annotations.NotNull;
import org.hzhub.system.utils.QwenFileUploadUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

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.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * 文件上传 服务层实现
 *
 * @author Lion Li
 */
@RequiredArgsConstructor
@Service
public class SysOssServiceImpl implements ISysOssService, OssService {

    private final SysOssMapper baseMapper;

    private final ConfigService configService;

    // 默认密钥
    private static String API_KEY;

    // 默认api路径地址
    private static String API_HOST;

    // 上传文件服务器地址
    @Value("${sys.upload.path}")
    private String UPLOAD_PATH;

    /**
     * 查询OSS对象存储列表
     *
     * @param bo        OSS对象存储分页查询对象
     * @param pageQuery 分页查询实体类
     * @return 结果
     */
    @Override
    public TableDataInfo<SysOssVo> queryPageList(SysOssBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<SysOss> lqw = buildQueryWrapper(bo);
        Page<SysOssVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        List<SysOssVo> filterResult = StreamUtils.toList(result.getRecords(), this::matchingUrl);
        result.setRecords(filterResult);
        return TableDataInfo.build(result);
    }

    /**
     * 根据一组 ossIds 获取对应的 SysOssVo 列表
     *
     * @param ossIds 一组文件在数据库中的唯一标识集合
     * @return 包含 SysOssVo 对象的列表
     */
    @Override
    public List<SysOssVo> listByIds(Collection<Long> ossIds) {
        List<SysOssVo> list = new ArrayList<>();
        SysOssServiceImpl ossService = SpringUtils.getAopProxy(this);
        for (Long id : ossIds) {
            SysOssVo vo = ossService.getById(id);
            if (ObjectUtil.isNotNull(vo)) {
                try {
                    list.add(this.matchingUrl(vo));
                } catch (Exception ignored) {
                    // 如果oss异常无法连接则将数据直接返回
                    list.add(vo);
                }
            }
        }
        return list;
    }

    /**
     * 根据一组 ossIds 获取对应文件的 URL 列表
     *
     * @param ossIds 以逗号分隔的 ossId 字符串
     * @return 以逗号分隔的文件 URL 字符串
     */
    @Override
    public String selectUrlByIds(String ossIds) {
        List<String> list = new ArrayList<>();
        SysOssServiceImpl ossService = SpringUtils.getAopProxy(this);
        for (Long id : StringUtils.splitTo(ossIds, Convert::toLong)) {
            SysOssVo vo = ossService.getById(id);
            if (ObjectUtil.isNotNull(vo)) {
                try {
                    list.add(this.matchingUrl(vo).getUrl());
                } catch (Exception ignored) {
                    // 如果oss异常无法连接则将数据直接返回
                    list.add(vo.getUrl());
                }
            }
        }
        return StringUtils.joinComma(list);
    }

    @Override
    public List<OssDTO> selectByIds(String ossIds) {
        List<OssDTO> list = new ArrayList<>();
        for (Long id : StringUtils.splitTo(ossIds, Convert::toLong)) {
            SysOssVo vo = SpringUtils.getAopProxy(this).getById(id);
            if (ObjectUtil.isNotNull(vo)) {
                try {
                    vo.setUrl(this.matchingUrl(vo).getUrl());
                    list.add(BeanUtil.toBean(vo, OssDTO.class));
                } catch (Exception ignored) {
                    // 如果oss异常无法连接则将数据直接返回
                    list.add(BeanUtil.toBean(vo, OssDTO.class));
                }
            }
        }
        return list;
    }

    private LambdaQueryWrapper<SysOss> buildQueryWrapper(SysOssBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<SysOss> lqw = Wrappers.lambdaQuery();
        lqw.like(StringUtils.isNotBlank(bo.getFileName()), SysOss::getFileName, bo.getFileName());
        lqw.like(StringUtils.isNotBlank(bo.getOriginalName()), SysOss::getOriginalName, bo.getOriginalName());
        lqw.eq(StringUtils.isNotBlank(bo.getFileSuffix()), SysOss::getFileSuffix, bo.getFileSuffix());
        lqw.eq(StringUtils.isNotBlank(bo.getUrl()), SysOss::getUrl, bo.getUrl());
        lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null,
            SysOss::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime"));
        lqw.eq(ObjectUtil.isNotNull(bo.getCreateBy()), SysOss::getCreateBy, bo.getCreateBy());
        lqw.eq(StringUtils.isNotBlank(bo.getService()), SysOss::getService, bo.getService());
        lqw.orderByAsc(SysOss::getOssId);
        return lqw;
    }

    /**
     * 根据 ossId 从缓存或数据库中获取 SysOssVo 对象
     *
     * @param ossId 文件在数据库中的唯一标识
     * @return SysOssVo 对象，包含文件信息
     */
    @Cacheable(cacheNames = CacheNames.SYS_OSS, key = "#ossId")
    @Override
    public SysOssVo getById(Long ossId) {
        return baseMapper.selectVoById(ossId);
    }


    /**
     * 文件下载方法，支持一次性下载完整文件
     *
     * @param ossId    OSS对象ID
     * @param response HttpServletResponse对象，用于设置响应头和向客户端发送文件内容
     */
    @Override
    public void download(Long ossId, HttpServletResponse response) throws IOException {
        SysOssVo sysOss = SpringUtils.getAopProxy(this).getById(ossId);
        if (ObjectUtil.isNull(sysOss)) {
            throw new ServiceException("文件数据不存在!");
        }
        FileUtils.setAttachmentResponseHeader(response, sysOss.getOriginalName());
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");
        OssClient storage = OssFactory.instance(sysOss.getService());
        storage.download(sysOss.getFileName(), response.getOutputStream(), response::setContentLengthLong);
    }

    /**
     * 上传 MultipartFile 到对象存储服务，并保存文件信息到数据库
     *
     * @param file 要上传的 MultipartFile 对象
     * @return 上传成功后的 SysOssVo 对象，包含文件信息
     * @throws ServiceException 如果上传过程中发生异常，则抛出 ServiceException 异常
     */
    @Override
    public SysOssVo upload(MultipartFile file) {
        String originalfileName = file.getOriginalFilename();
        String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length());
        OssClient storage = OssFactory.instance();
        UploadResult uploadResult;
        try {
            uploadResult = storage.uploadSuffix(file.getBytes(), suffix, ContentTypeUtil.getContentType(suffix, file.getContentType()));
        } catch (IOException e) {
            throw new ServiceException(e.getMessage());
        }
        SysOssExt ext1 = new SysOssExt();
        ext1.setFileSize(file.getSize());
        ext1.setContentType(file.getContentType());
        // 保存文件信息
        return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult, ext1);
    }

    /**
     * 上传 MultipartFile 到对象存储服务，并保存文件信息到数据库
     *
     * @param file 要上传的 MultipartFile 对象
     * @return 上传成功后的 OssDTO 对象，包含文件信息
     * @throws ServiceException 如果上传过程中发生异常，则抛出 ServiceException 异常
     */
    public OssDTO uploadFile(MultipartFile file) {
        SysOssVo sysOssVo = upload(file);
        return BeanUtil.toBean(sysOssVo, OssDTO.class);
    }

    /**
     * 上传文件至千问百炼平台
     * @param file 要上传的 MultipartFile 对象
     * @return 上传成功后的 SysOssVo 对象，包含文件信息
     */
    @Override
    public SysOssUploadVo fileUpload(MultipartFile file) {
        String originalName = file.getOriginalFilename();
        if (StringUtils.isEmpty(originalName)){
            throw new ServiceException("文件名不能为空");
        }
        int lastDotIndex = originalName != null ? originalName.lastIndexOf(".") : -1;
        String prefix = lastDotIndex > 0 ? originalName.substring(0, lastDotIndex) : "";
        String suffix = lastDotIndex > 0 ? originalName.substring(lastDotIndex) : "";
        try {
            // 确保上传目录存在
            Path uploadDir = Paths.get(UPLOAD_PATH);
            if (!Files.exists(uploadDir)) {
                Files.createDirectories(uploadDir);
            }
            // 生成上传文件名
            Path targetPath = uploadDir.resolve(originalName);
            // 直接保存文件
            File pathFile = targetPath.toFile();
            file.transferTo(pathFile);
            // 获取配置
            initConfig();
            // 使用工具类上传文件到阿里云
            String fileId = QwenFileUploadUtils.uploadFile(pathFile, API_HOST, API_KEY);
            if (StringUtils.isEmpty(fileId)) {
                throw new ServiceException("文件上传失败，未获取到fileId");
            }
            // 拿到上传地址的路径地址
            String filePath = targetPath.toAbsolutePath().toString();
            // 保存文件信息到数据库
            return buildEntity(filePath, fileId, suffix, prefix, originalName);
        } catch (IOException e) {
            throw new ServiceException("文件上传失败: " + e.getMessage());
        }
    }

    /**
     * 保存数据到数据库中
     * @param filePath 上传文件地址
     * @param fileId 百炼平台返回fileID
     * @param suffix 文件后缀
     * @param prefix 文件前缀
     * @param originalName 文件名称
     * @return SysOssUploadVo 上传返回VO对象
     */
    @NotNull
    private SysOssUploadVo buildEntity(String filePath, String fileId, String suffix, String prefix, String originalName) {
        String url = OssConstant.FILE_ID_PREFIX + fileId;
        SysOss oss = new SysOss();
        oss.setUrl(url);
        oss.setExt1(filePath);
        oss.setFileSuffix(suffix);
        oss.setFileName(prefix);
        oss.setOriginalName(originalName);
        oss.setService(OssConstant.DASH_SCOPE);
        baseMapper.insert(oss);
        SysOssUploadVo uploadVo = new SysOssUploadVo();
        BeanUtils.copyProperties(oss, uploadVo);
        uploadVo.setFileName(originalName);
        return uploadVo;
    }

    /**
     * 上传文件到对象存储服务，并保存文件信息到数据库
     *
     * @param file 要上传的文件对象
     * @return 上传成功后的 SysOssVo 对象，包含文件信息
     */
    @Override
    public SysOssVo upload(File file) {
        String originalfileName = file.getName();
        String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length());
        OssClient storage = OssFactory.instance();
        UploadResult uploadResult = storage.uploadSuffix(file, suffix);
        SysOssExt ext1 = new SysOssExt();
        ext1.setFileSize(file.length());
        // 保存文件信息
        return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult, ext1);
    }

    @NotNull
    private SysOssVo buildResultEntity(String originalfileName, String suffix, String configKey, UploadResult uploadResult, SysOssExt ext1) {
        SysOss oss = new SysOss();
        oss.setUrl(uploadResult.getUrl());
        oss.setFileSuffix(suffix);
        oss.setFileName(uploadResult.getFilename());
        oss.setOriginalName(originalfileName);
        oss.setService(configKey);
        oss.setExt1(JsonUtils.toJsonString(ext1));
        baseMapper.insert(oss);
        SysOssVo sysOssVo = MapstructUtils.convert(oss, SysOssVo.class);
        return this.matchingUrl(sysOssVo);
    }

    /**
     * 删除OSS对象存储
     *
     * @param ids     OSS对象ID串
     * @param isValid 判断是否需要校验
     * @return 结果
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if (isValid) {
            // 做一些业务上的校验,判断是否需要校验
        }
        List<SysOss> list = baseMapper.selectByIds(ids);
        for (SysOss sysOss : list) {
            OssClient storage = OssFactory.instance(sysOss.getService());
            storage.delete(sysOss.getUrl());
        }
        return baseMapper.deleteByIds(ids) > 0;
    }

    /**
     * 桶类型为 private 的URL 修改为临时URL时长为120s
     *
     * @param oss OSS对象
     * @return oss 匹配Url的OSS对象
     */
    private SysOssVo matchingUrl(SysOssVo oss) {
        OssClient storage = OssFactory.instance(oss.getService());
        // 仅修改桶类型为 private 的URL，临时URL时长为120s
        if (AccessPolicyType.PRIVATE == storage.getAccessPolicy()) {
            oss.setUrl(storage.getPrivateUrl(oss.getFileName(), Duration.ofSeconds(120)));
        }
        return oss;
    }

    /**
     * 初始化配置并返回API密钥和主机
     */
    private void initConfig() {
        String apiKey = configService.getConfigValue(OssConstant.CONFIG_NAME_KEY);
        if (StringUtils.isEmpty(apiKey)) {
            throw new ServiceException("请先配置Qwen上传文件相关API_KEY");
        }
        API_KEY = apiKey;
        String apiHost = configService.getConfigValue(OssConstant.CONFIG_NAME_URL);
        if (StringUtils.isEmpty(apiHost)) {
            throw new ServiceException("请先配置Qwen上传文件相关API_HOST");
        }
        API_HOST = apiHost;
    }
}
