package com.yd.oss.service.service.impl;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.CannedAccessControlList;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PutObjectRequest;
import com.yd.common.enums.CommonEnum;
import com.yd.common.utils.IpUtil;
import com.yd.common.utils.RandomStringGenerator;
import com.yd.oss.service.dto.FileMetadata;
import com.yd.oss.service.dto.OssUploadFileDto;
import com.yd.oss.service.dto.UploadResult;
import com.yd.oss.service.model.OssFile;
import com.yd.oss.service.model.OssOperationLog;
import com.yd.oss.service.model.OssProvider;
import com.yd.oss.service.service.IOssFileService;
import com.yd.oss.service.service.IOssProviderService;
import com.yd.oss.service.service.OssService;
import com.yd.oss.service.utils.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.URL;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.UUID;

@Service
@Primary
@Slf4j
public class AliYunOssServiceImpl implements OssService {

    @Autowired
    private IOssProviderService ossProviderService;

    @Autowired
    private IOssFileService ossFileService;

    @Autowired
    private OSS ossClient; // 注入OSS客户端

    @Autowired
    private String defaultBucket; // 注入默认存储桶

    @Autowired
    private String defaultEndpoint; // 注入默认服务端点

    @Autowired
    private OssProvider currentProvider; // 注入当前提供商

    /**
     * 上传文件
     * @param inputStream 文件流
     * @param fileName 原始文件名
     * @param bucketName OSS桶名
     * @param uploadUser 操作用户
     * @param type oss文件夹分类目录类型，例：如果是.xls那就是excel/
     * @return
     */
    @Override
    public OssUploadFileDto uploadFile(InputStream inputStream, String fileName,
                                       String bucketName, String uploadUser,String type) {
        OssUploadFileDto ossUploadFileDto = new OssUploadFileDto();
        long startTime = System.currentTimeMillis(); // 记录开始时间
        String operationResult = "success"; // 操作结果
        String errorMessage = null; // 错误信息
        OssFile ossFile = null; // 文件记录

        try {
            // 读取输入流到字节数组
            byte[] fileData = FileUtil.readInputStreamToBytes(inputStream);
            long fileSize = fileData.length;
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(fileData);

            // 生成文件key
            String fileKey = FileUtil.generateFileKey(fileName,type);
            String actualBucket = StringUtils.isNotBlank(bucketName) ? bucketName : defaultBucket;
            String fileType = FileUtil.getFileType(fileName);

            // 创建上传请求
            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentLength(fileSize);
            metadata.setContentType(FileUtil.getContentType(fileType));
            // 设置公共读权限（如果需要）
//            metadata.setObjectAcl(CannedAccessControlList.PublicRead);

            PutObjectRequest putObjectRequest = new PutObjectRequest(
                    actualBucket, fileKey, byteArrayInputStream, metadata);

            // 上传文件
            ossClient.putObject(putObjectRequest);

            // 创建文件记录
            ossFile = new OssFile();
            ossFile.setFileBizId(RandomStringGenerator.generateBizId16(CommonEnum.UID_TYPE_OSS_FILE.getCode()));
            ossFile.setFileKey(fileKey);
            ossFile.setOriginalName(fileName);
            ossFile.setFileSize(fileSize);
            ossFile.setFileType(fileType);
            ossFile.setProviderBizId(currentProvider.getProviderBizId());
            ossFile.setBucketName(actualBucket);
            ossFile.setUploadUser(uploadUser);

            // 保存文件元数据到数据库
            ossFileService.saveFileMetadata(ossFile);

            // 创建操作日志
            OssOperationLog operationLog = new OssOperationLog();
            operationLog.setFileBizId(ossFile.getFileBizId());
            operationLog.setOperationType("upload");
            operationLog.setOperationUser(uploadUser);
            operationLog.setOperationResult(operationResult);
            operationLog.setErrorMessage(errorMessage);
            operationLog.setClientIp(IpUtil.getClientIp());
            operationLog.setCostTime(System.currentTimeMillis() - startTime);

            // 保存操作日志到数据库
            ossFileService.saveOperationLog(operationLog);
            log.info("文件上传成功: {} -> {}/{}", fileName, actualBucket, fileKey);

            ossUploadFileDto.setFileBizId(ossFile.getFileBizId());
            ossUploadFileDto.setFileKey(fileKey);
            ossUploadFileDto.setOriginalName(fileName);
            // 如果设置了公共读，可以直接拼接URL
            String publicUrl = "https://" + actualBucket + "." + defaultEndpoint + "/" + fileKey;
            ossUploadFileDto.setUrl(publicUrl);
            return ossUploadFileDto;
        } catch (Exception e) {
            operationResult = "failure"; // 标记操作失败
            errorMessage = e.getMessage(); // 记录错误信息
            log.error("阿里云OSS上传文件失败: {}", fileName, e);
            throw new RuntimeException("阿里云OSS上传文件失败: " + fileName, e);
        } finally {
            // 关闭资源
            FileUtil.closeQuietly(inputStream);

            // 记录失败日志
            if ("failure".equals(operationResult)) {
                OssOperationLog operationLog = new OssOperationLog();
                if (ossFile != null) {
                    operationLog.setFileBizId(ossFile.getFileBizId());
                } else {
                    operationLog.setFileBizId("-1"); // 使用无效文件ID
                }
                operationLog.setOperationType("upload");
                operationLog.setOperationUser(uploadUser);
                operationLog.setOperationResult(operationResult);
                operationLog.setErrorMessage(errorMessage);
                operationLog.setClientIp(IpUtil.getClientIp());
                operationLog.setCostTime(System.currentTimeMillis() - startTime);

                // 保存操作日志到数据库
                ossFileService.saveOperationLog(operationLog);
            }
        }
    }

    /**
     * 上传文件（使用默认存储桶）
     * @param inputStream
     * @param fileName
     * @param uploadUser
     * @return
     */
    @Override
    public String uploadFile(InputStream inputStream, String fileName, String uploadUser) {
        // 调用重载方法，使用默认存储桶
//        return uploadFile(inputStream, fileName, defaultBucket, uploadUser);
        return null;
    }

    /**
     * 下载文件
     * @param fileKey
     * @param bucketName
     * @return
     */
    @Override
    public InputStream downloadFile(String fileKey, String bucketName) {
        long startTime = System.currentTimeMillis(); // 记录开始时间
        String operationResult = "success"; // 操作结果
        String errorMessage = null; // 错误信息
        OssFile ossFile = null; // 文件记录

        try {
            // 确定存储桶（如果未指定则使用默认存储桶）
            String actualBucket = StringUtils.isNotBlank(bucketName) ? bucketName : defaultBucket;

            // 获取文件信息
            ossFile = ossFileService.getFileByKey(fileKey);

            // 从阿里云OSS下载文件
            OSSObject ossObject = ossClient.getObject(actualBucket, fileKey);
            InputStream inputStream = ossObject.getObjectContent();

            // 创建操作日志
            OssOperationLog operationLog = new OssOperationLog();
            operationLog.setFileBizId(ossFile.getFileBizId());
            operationLog.setOperationType("download");
            operationLog.setOperationUser("system"); // 系统操作
            operationLog.setOperationResult(operationResult);
            operationLog.setErrorMessage(errorMessage);
            operationLog.setClientIp(IpUtil.getClientIp());
            operationLog.setCostTime(System.currentTimeMillis() - startTime);

            // 保存操作日志到数据库
            ossFileService.saveOperationLog(operationLog);

            log.info("文件下载成功: {}/{}", actualBucket, fileKey);
            return inputStream;
        } catch (Exception e) {
            operationResult = "failure"; // 标记操作失败
            errorMessage = e.getMessage(); // 记录错误信息
            log.error("阿里云OSS下载文件失败: {}", fileKey, e);
            throw new RuntimeException("阿里云OSS下载文件失败: " + fileKey, e);
        } finally {
            // 记录失败日志
            if ("failure".equals(operationResult)) {
                OssOperationLog operationLog = new OssOperationLog();
                if (ossFile != null) {
                    operationLog.setFileBizId(ossFile.getFileBizId());
                } else {
                    operationLog.setFileBizId("-1"); // 使用无效文件ID
                }
                operationLog.setOperationType("download");
                operationLog.setOperationUser("system");
                operationLog.setOperationResult(operationResult);
                operationLog.setErrorMessage(errorMessage);
                operationLog.setClientIp(IpUtil.getClientIp());
                operationLog.setCostTime(System.currentTimeMillis() - startTime);

                // 保存操作日志到数据库
                ossFileService.saveOperationLog(operationLog);
            }
        }
    }

    /**
     * 下载文件（使用默认存储桶）
     * @param fileKey
     * @return
     */
    @Override
    public InputStream downloadFile(String fileKey) {
        // 调用重载方法，使用默认存储桶
        return downloadFile(fileKey, defaultBucket);
    }

    /**
     * 删除文件
     * @param fileKey
     * @param bucketName
     * @param operator
     * @return
     */
    @Override
    public boolean deleteFile(String fileKey, String bucketName, String operator) {
        long startTime = System.currentTimeMillis(); // 记录开始时间
        String operationResult = "success"; // 操作结果
        String errorMessage = null; // 错误信息
        OssFile ossFile = null; // 文件记录

        try {
            // 确定存储桶（如果未指定则使用默认存储桶）
            String actualBucket = StringUtils.isNotBlank(bucketName) ? bucketName : defaultBucket;

            // 获取文件信息
            ossFile = ossFileService.getFileByKey(fileKey);

            // 从阿里云OSS删除文件 TODO
            ossClient.deleteObject(actualBucket, ossFile.getFileKey());

            // 标记文件为已删除
            ossFileService.markFileAsDeleted(fileKey);

            // 创建操作日志
            OssOperationLog operationLog = new OssOperationLog();
            operationLog.setFileBizId(ossFile.getFileBizId());
            operationLog.setOperationType("delete");
            operationLog.setOperationUser(operator);
            operationLog.setOperationResult(operationResult);
            operationLog.setErrorMessage(errorMessage);
            operationLog.setClientIp(IpUtil.getClientIp());
            operationLog.setCostTime(System.currentTimeMillis() - startTime);

            // 保存操作日志到数据库
            ossFileService.saveOperationLog(operationLog);

            log.info("文件删除成功: {}/{}", actualBucket, fileKey);
            return true;
        } catch (Exception e) {
            operationResult = "failure"; // 标记操作失败
            errorMessage = e.getMessage(); // 记录错误信息
            log.error("阿里云OSS删除文件失败: {}", fileKey, e);
            throw new RuntimeException("阿里云OSS删除文件失败: " + fileKey, e);
        } finally {
            // 记录失败日志
            if ("failure".equals(operationResult)) {
                OssOperationLog operationLog = new OssOperationLog();
                if (ossFile != null) {
                    operationLog.setFileBizId(ossFile.getFileBizId());
                } else {
                    operationLog.setFileBizId("-1"); // 使用无效文件ID
                }
                operationLog.setOperationType("delete");
                operationLog.setOperationUser(operator);
                operationLog.setOperationResult(operationResult);
                operationLog.setErrorMessage(errorMessage);
                operationLog.setClientIp(IpUtil.getClientIp());
                operationLog.setCostTime(System.currentTimeMillis() - startTime);

                // 保存操作日志到数据库
                ossFileService.saveOperationLog(operationLog);
            }
        }
    }

    /**
     * 删除文件（使用默认存储桶）
     * @param fileKey
     * @param operator
     * @return
     */
    @Override
    public boolean deleteFile(String fileKey, String operator) {
        // 调用重载方法，使用默认存储桶
        return deleteFile(fileKey, defaultBucket, operator);
    }

    /**
     * 获取文件URL
     * @param fileKey
     * @param bucketName
     * @param expiration
     * @return
     */
    @Override
    public String getFileUrl(String fileKey, String bucketName, Duration expiration) {
        try {
            // 确定存储桶（如果未指定则使用默认存储桶）
            String actualBucket = StringUtils.isNotBlank(bucketName) ? bucketName : defaultBucket;

            // 生成预签名URL（带过期时间）
            Date expirationDate = new Date(System.currentTimeMillis() + expiration.toMillis());
            URL url = ossClient.generatePresignedUrl(actualBucket, fileKey, expirationDate);

            return url.toString();
        } catch (Exception e) {
            log.error("阿里云OSS获取文件URL失败: {}", fileKey, e);
            throw new RuntimeException("阿里云OSS获取文件URL失败: " + fileKey, e);
        }
    }

    /**
     * 获取文件URL（使用默认存储桶）
     * @param fileKey
     * @param expiration
     * @return
     */
    @Override
    public String getFileUrl(String fileKey, Duration expiration) {
        // 调用重载方法，使用默认存储桶
        return getFileUrl(fileKey, defaultBucket, expiration);
    }

    /**
     * 判断文件是否存在
     * @param fileKey
     * @param bucketName
     * @return
     */
    @Override
    public boolean doesFileExist(String fileKey, String bucketName) {
        try {
            // 确定存储桶（如果未指定则使用默认存储桶）
            String actualBucket = StringUtils.isNotBlank(bucketName) ? bucketName : defaultBucket;

            // 检查文件是否存在
            return ossClient.doesObjectExist(actualBucket, fileKey);
        } catch (Exception e) {
            log.error("阿里云OSS检查文件存在失败: {}", fileKey, e);
            throw new RuntimeException("阿里云OSS检查文件存在失败: " + fileKey, e);
        }
    }

    /**
     * 判断文件是否存在（使用默认存储桶）
     * @param fileKey
     * @return
     */
    @Override
    public boolean doesFileExist(String fileKey) {
        // 调用重载方法，使用默认存储桶
        return doesFileExist(fileKey, defaultBucket);
    }

    /**
     * 获取文件元数据
     * @param fileKey
     * @param bucketName
     * @return
     */
    @Override
    public FileMetadata getFileMetadata(String fileKey, String bucketName) {
        try {
            // 确定存储桶（如果未指定则使用默认存储桶）
            String actualBucket = StringUtils.isNotBlank(bucketName) ? bucketName : defaultBucket;

            // 获取文件元数据
            ObjectMetadata objectMetadata = ossClient.getObjectMetadata(actualBucket, fileKey);

            // 转换为通用文件元数据格式
            FileMetadata metadata = new FileMetadata();
            metadata.setFileKey(fileKey);
            metadata.setFileSize(objectMetadata.getContentLength());
            metadata.setContentType(objectMetadata.getContentType());
//            metadata.setLastModified(objectMetadata.getLastModified());
            metadata.setEtag(objectMetadata.getETag());

            return metadata;
        } catch (Exception e) {
            log.error("阿里云OSS获取文件元数据失败: {}", fileKey, e);
            throw new RuntimeException("阿里云OSS获取文件元数据失败: " + fileKey, e);
        }
    }

    /**
     * 获取文件元数据（使用默认存储桶）
     * @param fileKey
     * @return
     */
    @Override
    public FileMetadata getFileMetadata(String fileKey) {
        // 调用重载方法，使用默认存储桶
        return getFileMetadata(fileKey, defaultBucket);
    }

    /**
     * 切换OSS提供商
     * @param providerName
     */
    @Override
    public void switchProvider(String providerName) {
        try {
            // 根据名称获取OSS提供商
            OssProvider provider = ossProviderService.getProviderByName(providerName);

            // 切换到新的提供商
            switchToProvider(provider);

            log.info("已切换到OSS提供商: {}", providerName);
        } catch (Exception e) {
            log.error("切换OSS提供商失败: {}", providerName, e);
            throw new RuntimeException("切换OSS提供商失败: " + providerName, e);
        }
    }

    /**
     * 切换OSS提供商（通过ID）
     * @param providerId
     */
    @Override
    public void switchProvider(Long providerId) {
        try {
            // 根据ID获取OSS提供商
            OssProvider provider = ossProviderService.getProviderById(providerId);

            // 切换到新的提供商
            switchToProvider(provider);

            log.info("已切换到OSS提供商ID: {}", providerId);
        } catch (Exception e) {
            log.error("切换OSS提供商失败: {}", providerId, e);
            throw new RuntimeException("切换OSS提供商失败: " + providerId, e);
        }
    }

    /**
     * 切换到指定的OSS提供商
     * @param provider
     */
    private void switchToProvider(OssProvider provider) {
        // 关闭旧的客户端
        if (this.ossClient != null) {
            this.ossClient.shutdown();
        }

        // 创建新的客户端
        this.currentProvider = provider;
        this.ossClient = new OSSClientBuilder().build(
                provider.getEndpoint(),
                provider.getAccessKey(),
                provider.getSecretKey()
        );
        this.defaultBucket = provider.getBucketName();
    }

    /**
     * 获取当前使用的提供商
     * @return
     */
    @Override
    public OssProvider getCurrentProvider() {
        return currentProvider;
    }

    /**
     * 设置默认存储桶
     * @param bucketName
     */
    @Override
    public void setDefaultBucket(String bucketName) {
        this.defaultBucket = bucketName;
        log.info("已设置默认存储桶: {}", bucketName);
    }

    /**
     * 获取默认存储桶
     * @return
     */
    @Override
    public String getDefaultBucket() {
        return defaultBucket;
    }

    /**
     * 获取上传结果（包含文件信息和访问URL）
     * @param fileKey
     * @param expiration
     * @return
     */
    @Override
    public UploadResult getUploadResult(String fileKey, Duration expiration) {
        try {
            // 获取文件信息
            OssFile file = ossFileService.getFileByKey(fileKey);

            // 生成访问URL
            String accessUrl = getFileUrl(fileKey, expiration);

            // 使用 split 方法以 "?" 为分隔符分割字符串
            String[] parts = accessUrl.split("\\?");
            String url = parts[0]; // 取问号之前的部分

            // 创建上传结果
            UploadResult result = new UploadResult();
            result.setFileKey(fileKey);
            result.setAccessUrl(accessUrl);
            result.setUrl(url);
            result.setFileSize(file.getFileSize());
            result.setFileType(file.getFileType());
            result.setOriginalName(file.getOriginalName());
            result.setUploadTime(file.getUploadTime());

            return result;
        } catch (Exception e) {
            log.error("获取上传结果失败: {}", fileKey, e);
            throw new RuntimeException("获取上传结果失败: " + fileKey, e);
        }
    }

    /**
     * 创建虚拟文件记录（用于操作日志）
     * @return
     */
    private OssFile createDummyOssFile() {
        OssFile file = new OssFile();
        file.setId(-1L); // 使用无效ID
        return file;
    }

    /**
     * 上传字节数组到OSS并设置为公共读权限
     * @param content 文件内容字节数组
     * @param fileName 文件名
     * @return 公共访问URL
     */
    public String upload(byte[] content, String fileName) {
        try {
            // 生成唯一文件名
            String objectName = "appointment/excel/" + UUID.randomUUID() + "/" + fileName;

            // 创建上传请求
            PutObjectRequest putObjectRequest = new PutObjectRequest(
                    defaultBucket,
                    objectName,
                    new ByteArrayInputStream(content)
            );

            // 上传文件
            ossClient.putObject(putObjectRequest);

            // 设置对象访问权限为公共读
            ossClient.setObjectAcl(defaultBucket, objectName, CannedAccessControlList.PublicRead);

            // 构造公共访问URL
            String publicUrl = "https://" + defaultBucket + "." + defaultEndpoint + "/" + objectName;

            return publicUrl;
        } catch (Exception e) {
            throw new RuntimeException("上传文件到OSS失败", e);
        }
    }

    /**
     * 上传输入流到OSS并设置为公共读权限
     * @param inputStream 文件输入流
     * @param fileName 文件名
     * @return 公共访问URL
     */
    public String upload(InputStream inputStream, String fileName) {
        try {
            // 生成唯一文件名
            String objectName = "appointment/excel/" + UUID.randomUUID() + "/" + fileName;

            // 创建上传请求
            PutObjectRequest putObjectRequest = new PutObjectRequest(
                    defaultBucket,
                    objectName,
                    inputStream
            );

            // 上传文件
            ossClient.putObject(putObjectRequest);

            // 设置对象访问权限为公共读
            ossClient.setObjectAcl(defaultBucket, objectName, CannedAccessControlList.PublicRead);

            // 构造公共访问URL
            String publicUrl = "https://" + defaultBucket + "." + defaultEndpoint + "/" + objectName;

            return publicUrl;
        } catch (Exception e) {
            throw new RuntimeException("上传文件到OSS失败", e);
        }
    }

}
