package com.example.oss.util;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.SetBucketCORSRequest;
import com.example.oss.config.OssConfig;
import com.example.oss.vo.OssTokenVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
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>
 * 仅包含前端直传所需的核心方法：
 * - generatePostObjectToken: 生成 PostObject 直传 Token
 * - configureBucketCors: 配置 Bucket CORS 规则
 * </p>
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class OssUtils {

    private final OssConfig ossConfig;

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

    // ==================== 前端直传核心方法 ====================

    /**
     * 生成阿里云 OSS PostObject 直传 Token
     * <p>
     * 用于前端直传文件到 OSS，无需经过后端中转。
     * 前端拿到此 Token 后，通过 FormData + POST 方式直接上传到 OSS。
     * </p>
     *
     * <h3>签名流程：</h3>
     * <ol>
     *   <li>构建 Policy JSON（包含过期时间和 key 约束）</li>
     *   <li>Base64 编码 Policy</li>
     *   <li>使用 AccessKeySecret + HMAC-SHA1 对 Base64(Policy) 计算签名</li>
     * </ol>
     *
     * @param fileName 原始文件名（如：图片.jpg）
     * @param dir      目录前缀（可选，如：avatar, course/cover）
     * @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);

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

        // 生成日期路径（如：2026-03-16/）
        String datePath = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + "/";

        // 组合存储路径
        String objectKey;
        if (dir != null && !dir.isEmpty()) {
            // 确保 dir 以/结尾
            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();

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

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

    /**
     * 配置 OSS Bucket CORS 规则，解决前端直传跨域问题
     * <p>
     * 需确保 OSS 账号有 oss:PutBucketCors 权限。
     * 也可在阿里云控制台手动配置。
     * </p>
     */
    public void configureBucketCors() {
        if (!Boolean.TRUE.equals(ossConfig.getCorsEnabled())) {
            return;
        }
        OSS ossClient = getOssClient();
        try {
            SetBucketCORSRequest request = new SetBucketCORSRequest(ossConfig.getBucketName());
            SetBucketCORSRequest.CORSRule rule = new SetBucketCORSRequest.CORSRule();

            // 允许的来源（开发：localhost，生产：需配置实际域名）
            String origins = ossConfig.getCorsAllowedOrigins();
            if (origins == null || origins.isBlank()) {
                origins = "http://localhost:5173,http://localhost:5174";
            }
            rule.setAllowedOrigins(Arrays.asList(origins.trim().split("\s*,\s*")));

            // 允许的方法：POST（直传）、GET、PUT、DELETE、HEAD
            rule.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "HEAD"));

            // 允许的请求头（* 表示全部，PostObject 需要）
            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
     *
     * @param objectKey  对象键
     * @param expiration 过期时间戳
     * @return Base64 编码的 Policy
     */
    private String buildPostPolicy(String objectKey, long expiration) {
        // ISO 8601 GMT 格式（阿里云要求 YYYY-MM-DDTHH:mm:ss.sssZ，必须以 Z 结尾）
        String expireTime = java.time.Instant.ofEpochMilli(expiration).toString();

        // 构建 Policy JSON
        String policyJson = String.format(
                "{\"expiration\":\"%s\",\"conditions\":[[\"eq\",\"$key\",\"%s\"]]}",
                expireTime,
                objectKey
        );

        // Base64 编码
        return Base64.getEncoder().encodeToString(policyJson.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 计算签名
     *
     * @param policy          Base64 编码的 Policy
     * @param accessKeySecret OSS 访问密钥
     * @return 签名（Base64 编码）
     */
    private String computeSignature(String policy, String accessKeySecret) {
        try {
            // 使用 HMAC-SHA1 算法
            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));

            // Base64 编码
            return Base64.getEncoder().encodeToString(signData);
        } catch (Exception e) {
            log.error("计算 OSS 签名失败：{}", e.getMessage(), e);
            throw new RuntimeException("计算 OSS 签名失败：" + e.getMessage(), e);
        }
    }

    /**
     * 检查文件扩展名是否允许
     *
     * @param filename 文件名
     * @return 是否允许
     */
    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));
    }

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