package com.lesingle.modules.oss.util;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.SetBucketCORSRequest;
import com.lesingle.modules.oss.config.OssConfig;
import com.lesingle.modules.oss.vo.OssTokenVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.UUID;

/**
 * 阿里云 OSS 工具类
 * <p>
 * 包含：前端直传签名生成、服务端上传、CORS 配置
 * </p>
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class OssUtils {

    private final OssConfig ossConfig;

    /**
     * 获取 OSS 客户端
     */
    private OSS getOssClient() {
        return new OSSClientBuilder().build(
                ossConfig.getEndpoint(),
                ossConfig.getAccessKeyId(),
                ossConfig.getAccessKeySecret()
        );
    }

    // ==================== 前端直传 ====================

    /**
     * 生成阿里云 OSS PostObject 直传 Token
     *
     * @param fileName 原始文件名
     * @param dir      目录前缀（可选）
     * @return OSS 直传 Token VO
     */
    public OssTokenVo generatePostObjectToken(String fileName, String dir) {
        // 校验文件名
        if (fileName == null || fileName.isEmpty()) {
            throw new IllegalArgumentException("文件名不能为空");
        }

        // 校验文件扩展名
        if (!isAllowedExtension(fileName)) {
            throw new IllegalArgumentException("不支持的文件类型：" + fileName);
        }

        // 获取文件扩展名
        String extension = getFileExtension(fileName);

        // 生成唯一文件名
        String uniqueFilename = UUID.randomUUID().toString().replace("-", "") + extension;

        // 生成日期路径
        String datePath = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + "/";

        // 组合存储路径
        String objectKey;
        if (dir != null && !dir.isEmpty()) {
            if (!dir.endsWith("/")) {
                dir = dir + "/";
            }
            objectKey = dir + datePath + uniqueFilename;
        } else {
            objectKey = datePath + uniqueFilename;
        }

        // 设置过期时间（30 秒）
        int expire = 30;
        long expiration = System.currentTimeMillis() + expire * 1000L;

        // 构建 PostPolicy
        String policy = buildPostPolicy(objectKey, expiration);

        // 计算签名
        String signature = computeSignature(policy, ossConfig.getAccessKeySecret());

        // 构建上传地址
        String host = "https://" + ossConfig.getBucketName() + "." + ossConfig.getEndpoint();

        return OssTokenVo.builder()
                .accessid(ossConfig.getAccessKeyId())
                .policy(policy)
                .signature(signature)
                .dir(dir != null ? dir : "")
                .host(host)
                .key(objectKey)
                .expire(expire)
                .build();
    }

    // ==================== 服务端上传（向后兼容） ====================

    /**
     * 服务端上传文件到 OSS
     *
     * @param file 上传的文件
     * @return 文件 URL
     */
    public String uploadFile(MultipartFile file) {
        String originalFilename = file.getOriginalFilename();
        String ext = getFileExtension(originalFilename);

        String datePath = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
        String uniqueName = UUID.randomUUID().toString().replace("-", "") + ext;
        String key = "server-upload/" + datePath + "/" + uniqueName;

        OSS ossClient = getOssClient();
        try {
            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentLength(file.getSize());
            metadata.setContentType(file.getContentType());

            PutObjectRequest putRequest = new PutObjectRequest(
                    ossConfig.getBucketName(), key, file.getInputStream(), metadata);
            ossClient.putObject(putRequest);

            String url = "https://" + ossConfig.getBucketName() + "." + ossConfig.getEndpoint() + "/" + key;
            log.info("文件上传至 OSS 成功: {}", url);
            return url;
        } catch (IOException e) {
            log.error("文件上传至 OSS 失败", e);
            throw new RuntimeException("文件上传失败", e);
        } finally {
            ossClient.shutdown();
        }
    }

    // ==================== CORS 配置 ====================

    /**
     * 配置 OSS Bucket CORS 规则
     */
    public void configureBucketCors() {
        if (!Boolean.TRUE.equals(ossConfig.getCorsEnabled())) {
            log.info("OSS CORS 自动配置已禁用");
            return;
        }

        OSS ossClient = getOssClient();
        try {
            SetBucketCORSRequest request = new SetBucketCORSRequest(ossConfig.getBucketName());
            SetBucketCORSRequest.CORSRule rule = new SetBucketCORSRequest.CORSRule();

            String origins = ossConfig.getCorsAllowedOrigins();
            if (origins == null || origins.isBlank()) {
                origins = "http://localhost:3000,http://localhost:5173";
            }
            rule.setAllowedOrigins(Arrays.asList(origins.trim().split("\\s*,\\s*")));
            rule.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "HEAD"));
            rule.setAllowedHeaders(Arrays.asList("*"));
            rule.setExposeHeaders(Arrays.asList("ETag", "x-oss-request-id"));
            rule.setMaxAgeSeconds(600);

            request.setCorsRules(List.of(rule));
            ossClient.setBucketCORS(request);
            log.info("OSS Bucket CORS 配置成功，允许来源: {}", origins);
        } catch (Exception e) {
            log.warn("OSS Bucket CORS 配置失败（可在阿里云控制台手动配置）: {}", e.getMessage());
        } finally {
            ossClient.shutdown();
        }
    }

    // ==================== 私有辅助方法 ====================

    /**
     * 构建 PostPolicy
     */
    private String buildPostPolicy(String objectKey, long expiration) {
        String expireTime = java.time.Instant.ofEpochMilli(expiration).toString();
        String policyJson = String.format(
                "{\"expiration\":\"%s\",\"conditions\":[[\"eq\",\"$key\",\"%s\"]]}",
                expireTime, objectKey
        );
        return Base64.getEncoder().encodeToString(policyJson.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 计算签名（HMAC-SHA1）
     */
    private String computeSignature(String policy, String accessKeySecret) {
        try {
            Mac mac = Mac.getInstance("HmacSHA1");
            SecretKeySpec keySpec = new SecretKeySpec(
                    accessKeySecret.getBytes(StandardCharsets.UTF_8), "HmacSHA1");
            mac.init(keySpec);
            byte[] signData = mac.doFinal(policy.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(signData);
        } catch (Exception e) {
            log.error("计算 OSS 签名失败：{}", e.getMessage(), e);
            throw new RuntimeException("计算 OSS 签名失败：" + e.getMessage(), e);
        }
    }

    /**
     * 检查文件扩展名是否允许
     */
    private boolean isAllowedExtension(String filename) {
        if (filename == null || filename.isEmpty()) {
            return false;
        }
        String extension = getFileExtension(filename).toLowerCase();
        return Arrays.stream(ossConfig.getAllowedExtensions())
                .anyMatch(allowed -> allowed.toLowerCase().equals(extension));
    }

    /**
     * 获取文件扩展名（包含点）
     */
    private String getFileExtension(String filename) {
        if (filename == null || filename.isEmpty()) {
            return "";
        }
        int lastDot = filename.lastIndexOf(".");
        if (lastDot == -1) {
            return "";
        }
        return filename.substring(lastDot);
    }
}
