package com.lesingle.edu.common.util;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.*;
import com.lesingle.edu.common.config.OssConfig;
import com.lesingle.edu.dto.response.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.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URL;
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>
 * 封装文件上传、下载、删除等常用操作
 * </p>
 *
 * @author reading-platform
 * @since 2026-03-13
 */
@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()
        );
    }

    /**
     * 上传文件（MultipartFile）
     *
     * @param file 上传的文件
     * @return 文件访问 URL
     */
    public String uploadFile(MultipartFile file) {
        return uploadFile(file, null);
    }

    /**
     * 上传文件（MultipartFile，自定义路径）
     *
     * @param file       上传的文件
     * @param customPath 自定义存储路径（如："avatar/"）
     * @return 文件访问 URL
     */
    public String uploadFile(MultipartFile file, String customPath) {
        if (file == null || file.isEmpty()) {
            throw new IllegalArgumentException("上传文件不能为空");
        }

        // 校验文件大小
        if (file.getSize() > ossConfig.getMaxFileSize()) {
            throw new IllegalArgumentException("文件大小超过限制：" + ossConfig.getMaxFileSize() / 1024 / 1024 + "MB");
        }

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

        try {
            InputStream inputStream = file.getInputStream();
            String objectKey = generateObjectKey(originalFilename, customPath);

            OSS ossClient = getOssClient();
            try {
                // 创建上传请求
                PutObjectRequest putObjectRequest = new PutObjectRequest(
                        ossConfig.getBucketName(),
                        objectKey,
                        inputStream
                );

                // 设置文件元数据
                ObjectMetadata metadata = new ObjectMetadata();
                metadata.setContentType(getContentType(originalFilename));
                putObjectRequest.setMetadata(metadata);

                // 执行上传
                ossClient.putObject(putObjectRequest);

                // 返回文件访问 URL
                String fileUrl = getFileUrl(objectKey);
                log.info("文件上传成功：{}, URL: {}", objectKey, fileUrl);
                return fileUrl;
            } finally {
                ossClient.shutdown();
            }
        } catch (Exception e) {
            log.error("文件上传失败：{}", e.getMessage(), e);
            throw new RuntimeException("文件上传失败：" + e.getMessage(), e);
        }
    }

    /**
     * 上传文件（字节数组）
     *
     * @param content    文件内容
     * @param fileName   文件名（带扩展名）
     * @param customPath 自定义存储路径
     * @return 文件访问 URL
     */
    public String uploadFile(byte[] content, String fileName, String customPath) {
        if (content == null || content.length == 0) {
            throw new IllegalArgumentException("上传内容不能为空");
        }

        // 校验文件大小
        if (content.length > ossConfig.getMaxFileSize()) {
            throw new IllegalArgumentException("文件大小超过限制：" + ossConfig.getMaxFileSize() / 1024 / 1024 + "MB");
        }

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

        OSS ossClient = getOssClient();
        try {
            String objectKey = generateObjectKey(fileName, customPath);
            InputStream inputStream = new ByteArrayInputStream(content);

            PutObjectRequest putObjectRequest = new PutObjectRequest(
                    ossConfig.getBucketName(),
                    objectKey,
                    inputStream
            );

            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentType(getContentType(fileName));
            putObjectRequest.setMetadata(metadata);

            ossClient.putObject(putObjectRequest);

            String fileUrl = getFileUrl(objectKey);
            log.info("文件上传成功：{}, URL: {}", objectKey, fileUrl);
            return fileUrl;
        } catch (Exception e) {
            log.error("文件上传失败：{}", e.getMessage(), e);
            throw new RuntimeException("文件上传失败：" + e.getMessage(), e);
        } finally {
            ossClient.shutdown();
        }
    }

    /**
     * 删除文件
     *
     * @param fileUrl 文件 URL 或对象键
     */
    public void deleteFile(String fileUrl) {
        if (fileUrl == null || fileUrl.isEmpty()) {
            return;
        }

        String objectKey = extractObjectKey(fileUrl);
        if (objectKey == null) {
            objectKey = fileUrl;
        }

        OSS ossClient = getOssClient();
        try {
            ossClient.deleteObject(ossConfig.getBucketName(), objectKey);
            log.info("文件删除成功：{}", objectKey);
        } catch (Exception e) {
            log.error("文件删除失败：{}", e.getMessage(), e);
            throw new RuntimeException("文件删除失败：" + e.getMessage(), e);
        } finally {
            ossClient.shutdown();
        }
    }

    /**
     * 批量删除文件
     *
     * @param fileUrls 文件 URL 列表
     * @return 删除失败的文件列表
     */
    public List<String> deleteFiles(List<String> fileUrls) {
        OSS ossClient = getOssClient();
        try {
            // 过滤并提取有效的 objectKey
            java.util.List<String> keys = new java.util.ArrayList<>();
            for (String fileUrl : fileUrls) {
                String objectKey = extractObjectKey(fileUrl);
                if (objectKey != null) {
                    keys.add(objectKey);
                }
            }

            if (keys.isEmpty()) {
                return fileUrls;
            }

            DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(ossConfig.getBucketName());
            deleteObjectsRequest.setQuiet(true);
            deleteObjectsRequest.setKeys(keys);

            DeleteObjectsResult deleteObjectsResult = ossClient.deleteObjects(deleteObjectsRequest);
            log.info("批量删除文件完成，成功删除：{} 个", deleteObjectsResult.getDeletedObjects().size());

            // 返回删除失败的文件
            return fileUrls.stream()
                    .filter(url -> !deleteObjectsResult.getDeletedObjects().contains(extractObjectKey(url)))
                    .toList();
        } catch (Exception e) {
            log.error("批量删除文件失败：{}", e.getMessage(), e);
            throw new RuntimeException("批量删除文件失败：" + e.getMessage(), e);
        } finally {
            ossClient.shutdown();
        }
    }

    /**
     * 获取文件信息
     *
     * @param objectKey 对象键
     * @return 文件元数据
     */
    public ObjectMetadata getFileMetadata(String objectKey) {
        OSS ossClient = getOssClient();
        try {
            return ossClient.getObjectMetadata(ossConfig.getBucketName(), objectKey);
        } catch (Exception e) {
            log.error("获取文件元数据失败：{}", e.getMessage(), e);
            throw new RuntimeException("获取文件元数据失败：" + e.getMessage(), e);
        } finally {
            ossClient.shutdown();
        }
    }

    /**
     * 获取文件访问 URL（带签名，有时效性）
     *
     * @param objectKey 对象键
     * @param expiration 过期时间（秒）
     * @return 签名 URL
     */
    public String getSignedUrl(String objectKey, Integer expiration) {
        OSS ossClient = getOssClient();
        try {
            // 设置 URL 过期时间为 expiration 秒
            URL url = ossClient.generatePresignedUrl(
                    ossConfig.getBucketName(),
                    objectKey,
                    new java.util.Date(System.currentTimeMillis() + expiration * 1000L)
            );
            return url.toString();
        } catch (Exception e) {
            log.error("生成签名 URL 失败：{}", e.getMessage(), e);
            throw new RuntimeException("生成签名 URL 失败：" + e.getMessage(), e);
        } finally {
            ossClient.shutdown();
        }
    }

    /**
     * 检查文件是否存在
     *
     * @param objectKey 对象键
     * @return 是否存在
     */
    public boolean doesObjectExist(String objectKey) {
        OSS ossClient = getOssClient();
        try {
            return ossClient.doesObjectExist(ossConfig.getBucketName(), objectKey);
        } catch (Exception e) {
            log.error("检查文件存在失败：{}", e.getMessage(), e);
            return false;
        } finally {
            ossClient.shutdown();
        }
    }

    /**
     * 生成对象键（存储路径）
     *
     * @param filename   原始文件名
     * @param customPath 自定义路径
     * @return 对象键
     */
    private String generateObjectKey(String filename, String customPath) {
        // 获取文件扩展名
        String extension = getFileExtension(filename);

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

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

        // 组合完整路径
        if (customPath != null && !customPath.isEmpty()) {
            return customPath + datePath + uniqueFilename;
        }
        return datePath + uniqueFilename;
    }

    /**
     * 从 URL 中提取对象键
     *
     * @param fileUrl 文件 URL
     * @return 对象键
     */
    private String extractObjectKey(String fileUrl) {
        if (fileUrl == null || fileUrl.isEmpty()) {
            return null;
        }

        try {
            // 如果是完整 URL，提取路径部分
            if (fileUrl.startsWith("http://") || fileUrl.startsWith("https://")) {
                URL url = new URL(fileUrl);
                String path = url.getPath();
                // 移除开头的 '/'
                if (path.startsWith("/")) {
                    path = path.substring(1);
                }
                return path;
            }
            return fileUrl;
        } catch (Exception e) {
            log.warn("解析文件 URL 失败：{}", e.getMessage());
            return fileUrl;
        }
    }

    /**
     * 获取文件访问 URL（公开读）
     *
     * @param objectKey 对象键
     * @return 文件访问 URL
     */
    private String getFileUrl(String objectKey) {
        return "https://" + ossConfig.getBucketName() + "." + ossConfig.getEndpoint() + "/" + objectKey;
    }

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

    /**
     * 根据文件扩展名获取 Content-Type
     *
     * @param filename 文件名
     * @return Content-Type
     */
    private String getContentType(String filename) {
        String extension = getFileExtension(filename).toLowerCase();
        return switch (extension) {
            case ".jpg", ".jpeg" -> "image/jpeg";
            case ".png" -> "image/png";
            case ".gif" -> "image/gif";
            case ".bmp" -> "image/bmp";
            case ".webp" -> "image/webp";
            case ".pdf" -> "application/pdf";
            case ".doc", ".docx" -> "application/msword";
            case ".xls", ".xlsx" -> "application/vnd.ms-excel";
            case ".ppt", ".pptx" -> "application/vnd.ms-powerpoint";
            case ".mp4" -> "video/mp4";
            case ".avi" -> "video/x-msvideo";
            case ".mov" -> "video/quicktime";
            case ".wmv" -> "video/x-ms-wmv";
            case ".mp3" -> "audio/mpeg";
            case ".wav" -> "audio/x-wav";
            case ".txt" -> "text/plain";
            default -> "application/octet-stream";
        };
    }

    /**
     * 生成阿里云 OSS PostObject 直传 Token
     * <p>
     * 用于前端直传文件到 OSS，无需经过后端中转
     * </p>
     *
     * @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();
    }

    /**
     * 构建 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);
        }
    }

    /**
     * 配置 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();
        }
    }
}
