package com.fs.file.service.impl;

import com.fs.common.exception.CustomException;
import com.fs.file.domain.MiniappUploadFile;
import com.fs.file.mapper.MiniappUploadFileMapper;
import com.fs.file.service.IMiniappUploadService;
import com.fs.file.vo.CosPostPolicyVO;
import com.fs.file.vo.CosSTSTokenVO;
import com.fs.file.vo.UploadCompleteReqVO;
import com.qcloud.cos.utils.Jackson;
import com.tencent.cloud.CosStsClient;
import com.tencent.cloud.Credentials;
import com.tencent.cloud.Response;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * 小程序大文件上传服务实现
 *
 * @author fs
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class MiniappUploadServiceImpl implements IMiniappUploadService {

    private final MiniappUploadFileMapper miniappUploadFileMapper;

    @Value("${cos.secret-id:}")
    private String secretId;

    @Value("${cos.secret-key:}")
    private String secretKey;

    @Value("${cos.bucket:}")
    private String bucket;

    @Value("${cos.region:}")
    private String region;

    @Value("${cos.domain:}")
    private String domain;

    @Value("${cos.upload-prefix:miniapp/upload/}")
    private String uploadPrefix;

    /**
     * 允许上传的文件扩展名白名单
     */
    private static final Set<String> EXT_WHITELIST = new HashSet<>(Arrays.asList(
            "jpg", "jpeg", "png", "gif", "bmp", "webp",
            "mp4", "mov", "avi", "wmv", "flv",
            "pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx"
    ));

    @Override
    public CosSTSTokenVO getSTSToken(String userId) {
        try {
            // 构建临时密钥请求参数
            TreeMap<String, Object> config = new TreeMap<>();
            config.put("SecretId", secretId);
            config.put("SecretKey", secretKey);

            // 临时密钥有效时长，单位是秒，默认 1800 秒（30分钟）
            config.put("durationSeconds", 1800);

            config.put("bucket", bucket);
            config.put("region", region);

            // 生成用户专属上传路径
            String userPrefix = uploadPrefix + userId + "/";

            // 允许的操作权限列表
            String[] allowActions = new String[]{
                // 简单上传
                "name/cos:PutObject",
                "name/cos:PostObject",
                // 分片上传
                "name/cos:InitiateMultipartUpload",
                "name/cos:ListMultipartUploads",
                "name/cos:ListParts",
                "name/cos:UploadPart",
                "name/cos:CompleteMultipartUpload",
                "name/cos:AbortMultipartUpload"
            };
            config.put("allowActions", allowActions);

            // 临时密钥生效条件，关于condition的详细设置规则和COS支持的condition类型可以参考
            // https://cloud.tencent.com/document/product/436/71306
            String[] allowPrefixes = new String[]{
                userPrefix + "*"
            };
            config.put("allowPrefixes", allowPrefixes);

            // 调用腾讯云 STS 获取临时密钥
            Response credential = CosStsClient.getCredential(config);
            Credentials credentials = credential.credentials;

            log.info("获取COS临时密钥成功, userId={}, prefix={}", userId, userPrefix);

            // 构建响应对象
            CosSTSTokenVO vo = new CosSTSTokenVO();
            vo.setTmpSecretId(credentials.tmpSecretId);
            vo.setTmpSecretKey(credentials.tmpSecretKey);
            vo.setSessionToken(credentials.sessionToken);
            vo.setExpiration(Instant.parse(credential.expiration).getEpochSecond());
            vo.setStartTime(credential.startTime);
            vo.setBucket(bucket);
            vo.setRegion(region);
            vo.setPrefix(userPrefix);

            return vo;
        } catch (Exception e) {
            log.error("获取COS临时密钥失败, userId={}", userId, e);
            throw new CustomException("获取上传凭证失败");
        }
    }

    @Override
    public CosPostPolicyVO generatePostPolicy(String userId, String ext) {
        try {
            // 参数校验
            if (ext == null || ext.trim().isEmpty()) {
                throw new CustomException("文件扩展名不能为空");
            }
            ext = ext.toLowerCase().trim();

            // 检查扩展名白名单
            if (!EXT_WHITELIST.contains(ext)) {
                throw new CustomException("不支持的文件类型: " + ext);
            }

            // 生成 COS 文件路径
            String cosKey = generateCosKey(userId, ext);

            // 当前时间和过期时间（15分钟有效期）
            long now = System.currentTimeMillis() / 1000;
            long exp = now + 900; // 15分钟
            String qKeyTime = now + ";" + exp;
            String qSignAlgorithm = "sha1";

            // 构建 Policy JSON
            Map<String, Object> policyMap = new LinkedHashMap<>();
            policyMap.put("expiration", Instant.ofEpochSecond(exp)
                    .atZone(ZoneOffset.UTC)
                    .format(DateTimeFormatter.ISO_INSTANT));

            List<Object> conditions = new ArrayList<>();
            conditions.add(createCondition("q-sign-algorithm", qSignAlgorithm));
            conditions.add(createCondition("q-ak", secretId));
            conditions.add(createCondition("q-sign-time", qKeyTime));
            conditions.add(createCondition("bucket", bucket));
            conditions.add(createCondition("key", cosKey));
            policyMap.put("conditions", conditions);

            String policyJson = Jackson.toJsonString(policyMap);
            String policyBase64 = Base64.getEncoder().encodeToString(policyJson.getBytes(StandardCharsets.UTF_8));

            // 步骤一：生成 SignKey
            String signKey = hmacSha1(secretKey, qKeyTime);

            // 步骤二：生成 StringToSign (对 policy 进行 SHA1)
            String stringToSign = sha1(policyJson);

            // 步骤三：生成 Signature
            String qSignature = hmacSha1(signKey, stringToSign);

            // 构建响应对象
            CosPostPolicyVO vo = new CosPostPolicyVO();
            vo.setUrl("https://" + bucket + ".cos." + region + ".myqcloud.com");
            vo.setKey(cosKey);
            vo.setQAk(secretId);
            vo.setQSignAlgorithm(qSignAlgorithm);
            vo.setQKeyTime(qKeyTime);
            vo.setQSignature(qSignature);
            vo.setPolicy(policyBase64);
            vo.setBucket(bucket);
            vo.setRegion(region);

            log.info("生成POST Policy成功, userId={}, key={}", userId, cosKey);
            return vo;

        } catch (CustomException e) {
            throw e;
        } catch (Exception e) {
            log.error("生成POST Policy失败, userId={}, ext={}", userId, ext, e);
            throw new CustomException("生成上传凭证失败");
        }
    }

    /**
     * 生成 COS 文件路径
     */
    private String generateCosKey(String userId, String ext) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss_");
        String timestamp = Instant.now().atZone(ZoneOffset.of("+8")).format(formatter);
        String random = String.format("%06d", (int) (Math.random() * 1000000));
        return uploadPrefix + userId  + "IMG_" + timestamp + random + "." + ext;
    }

    /**
     * 创建 Policy 条件
     */
    private Map<String, String> createCondition(String key, String value) {
        Map<String, String> condition = new LinkedHashMap<>();
        condition.put(key, value);
        return condition;
    }

    /**
     * HMAC-SHA1 加密
     */
    private String hmacSha1(String key, String data) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA1");
        SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA1");
        mac.init(secretKeySpec);
        byte[] hash = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
        return bytesToHex(hash);
    }

    /**
     * SHA1 哈希
     */
    private String sha1(String data) throws Exception {
        MessageDigest digest = MessageDigest.getInstance("SHA-1");
        byte[] hash = digest.digest(data.getBytes(StandardCharsets.UTF_8));
        return bytesToHex(hash);
    }

    /**
     * 字节数组转十六进制字符串
     */
    private String bytesToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) {
                hexString.append('0');
            }
            hexString.append(hex);
        }
        return hexString.toString();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public MiniappUploadFile uploadComplete(String userId, UploadCompleteReqVO reqVO) {
        // 检查是否已存在（秒传）
        if (reqVO.getFileMd5() != null && !reqVO.getFileMd5().isEmpty()) {
            MiniappUploadFile existFile = miniappUploadFileMapper.selectByMd5(reqVO.getFileMd5(), userId);
            if (existFile != null) {
                log.info("文件秒传成功, userId={}, md5={}, fileId={}", userId, reqVO.getFileMd5(), existFile.getId());
                return existFile;
            }
        }

        // 创建新记录
        MiniappUploadFile file = new MiniappUploadFile();
        file.setUserId(userId);
        file.setFileName(reqVO.getFileName());
        file.setFileSize(reqVO.getFileSize());
        file.setFileType(reqVO.getFileType());
        file.setFileExt(reqVO.getFileExt());
        file.setCosKey(reqVO.getCosKey());
        file.setCosUrl(reqVO.getCosUrl());
        file.setFileMd5(reqVO.getFileMd5());
        file.setBizType(reqVO.getBizType());
        file.setBizId(reqVO.getBizId());
        file.setStatus(1); // 上传成功
        file.setRemark(reqVO.getRemark());
        file.setCompanyId(reqVO.getCompanyId());

        miniappUploadFileMapper.insert(file);
        log.info("文件上传记录保存成功, userId={}, fileId={}, fileName={}", userId, file.getId(), file.getFileName());

        return file;
    }

    @Override
    public MiniappUploadFile checkFastUpload(String userId, String fileMd5) {
        return miniappUploadFileMapper.selectByMd5(fileMd5, userId);
    }

    @Override
    public List<MiniappUploadFile> listByBiz(String userId, String bizType, String bizId) {
        return miniappUploadFileMapper.selectByBiz(bizType, bizId, userId);
    }

    @Override
    public List<MiniappUploadFile> listByUserId(String userId) {
        return miniappUploadFileMapper.selectByUserId(userId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteById(String userId, Long id) {
        // 查询文件信息，验证归属
        MiniappUploadFile file = miniappUploadFileMapper.selectById(id);
        if (file == null) {
            throw new CustomException("文件不存在");
        }
        if (!file.getUserId().equals(userId)) {
            throw new CustomException("无权限删除此文件");
        }

        int result = miniappUploadFileMapper.deleteById(id);
        log.info("删除文件记录, userId={}, fileId={}, result={}", userId, id, result);

        // TODO: 可以在这里添加异步删除 COS 文件的逻辑

        return result > 0;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteByIds(String userId, List<Long> ids) {
        // 批量验证权限
        for (Long id : ids) {
            MiniappUploadFile file = miniappUploadFileMapper.selectById(id);
            if (file != null && !file.getUserId().equals(userId)) {
                throw new CustomException("无权限删除部分文件");
            }
        }

        int result = miniappUploadFileMapper.deleteByIds(ids);
        log.info("批量删除文件记录, userId={}, count={}", userId, result);

        return result > 0;
    }



}
