package com.dne.ems.service.impl;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import com.dne.ems.exception.FileStorageException;
import com.dne.ems.model.Attachment;
import com.dne.ems.model.Feedback;
import com.dne.ems.repository.AttachmentRepository;
import com.dne.ems.service.FileStorageService;

import jakarta.annotation.PostConstruct;

/**
 * 文件存储服务实现类，处理文件上传和下载
 * 
 * <p>该服务负责安全地存储用户上传的文件，管理文件与业务实体的关联，
 * 并提供文件下载功能。实现了文件类型验证、路径安全检查等安全措施。</p>
 *
 * @author DNE开发团队
 * @version 1.0
 * @since 2024
 */
@Service
public class FileStorageServiceImpl implements FileStorageService {

    private final Path fileStorageLocation;
    private final AttachmentRepository attachmentRepository;

    // Whitelist of allowed file extensions and content types for security
    private static final List<String> ALLOWED_EXTENSIONS = Arrays.asList(".png", ".jpg", ".jpeg", ".gif", ".pdf", ".doc", ".docx");
    private static final List<String> ALLOWED_CONTENT_TYPES = Arrays.asList(
            "image/png", "image/jpeg", "image/gif", "application/pdf", 
            "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
    );

    public FileStorageServiceImpl(@Value("${file.upload-dir}") String uploadDir, AttachmentRepository attachmentRepository) {
        this.attachmentRepository = attachmentRepository;
        this.fileStorageLocation = Paths.get(uploadDir).toAbsolutePath().normalize();
    }

    /**
     * 初始化文件存储目录
     * 
     * <p>在服务启动时创建文件存储目录（如果不存在）。</p>
     * 
     * @throws FileStorageException 如果无法创建存储目录
     */
    @PostConstruct
    public void init() {
        try {
            Files.createDirectories(this.fileStorageLocation);
        } catch (IOException ex) {
            throw new FileStorageException("Could not create the directory where the uploaded files will be stored.", ex);
        }
    }

    /**
     * {@inheritDoc}
     *
     * <p>实现细节：
     * <ol>
     *   <li>验证文件存储位置配置</li>
     *   <li>检查文件是否为空</li>
     *   <li>清理文件名并验证安全性</li>
     *   <li>验证文件扩展名和内容类型</li>
     *   <li>生成唯一存储文件名</li>
     *   <li>保存文件到存储位置</li>
     *   <li>创建并保存附件记录</li>
     * </ol>
     *
     * <p>安全措施：
     * <ul>
     *   <li>防止路径遍历攻击</li>
     *   <li>限制允许的文件类型</li>
     *   <li>使用唯一文件名防止冲突</li>
     * </ul>
     */
    @Override
    @Deprecated
    public Attachment storeFileAndCreateAttachment(MultipartFile file, Feedback feedback) {
        String storedFilename = storeFile(file);

        Attachment attachment = Attachment.builder()
                .fileName(StringUtils.cleanPath(file.getOriginalFilename()))
                .storedFileName(storedFilename)
                .fileType(file.getContentType())
                .fileSize(file.getSize())
                .feedback(feedback)
                .build();

        return attachmentRepository.save(attachment);
    }

    /**
     * {@inheritDoc}
     *
     * <p>实现细节：
     * <ol>
     *   <li>验证文件存储位置配置</li>
     *   <li>检查文件是否为空</li>
     *   <li>清理文件名并验证安全性</li>
     *   <li>验证文件扩展名和内容类型</li>
     *   <li>生成唯一存储文件名</li>
     *   <li>保存文件到存储位置</li>
     * </ol>
     */
    @Override
    public String storeFile(MultipartFile file) {
        if (this.fileStorageLocation == null) {
            throw new FileStorageException("File storage location is not configured.");
        }
        if (file == null || file.isEmpty()) {
            throw new FileStorageException("Cannot store an empty file.");
        }

        String originalFilename = file.getOriginalFilename();
        if (originalFilename == null || originalFilename.trim().isEmpty()) {
            throw new FileStorageException("File name is invalid or not provided.");
        }
        String cleanedFilename = StringUtils.cleanPath(originalFilename);

        // Security Check: Validate filename
        if (cleanedFilename.contains("..")) {
            throw new FileStorageException("Cannot store file with relative path outside current directory " + cleanedFilename);
        }

        // Security Check: Validate file extension and content type against a whitelist
        String fileExtension = getFileExtension(cleanedFilename);
        if (!ALLOWED_EXTENSIONS.contains(fileExtension.toLowerCase())) {
            throw new FileStorageException("Invalid file extension. Only " + ALLOWED_EXTENSIONS + " are allowed.");
        }
        
        String contentType = file.getContentType();
        if (!ALLOWED_CONTENT_TYPES.contains(contentType)) {
            throw new FileStorageException("Invalid file content type. Only " + ALLOWED_CONTENT_TYPES + " are allowed.");
        }

        try {
            // Generate a unique file name to avoid collisions
            String storedFilename = UUID.randomUUID().toString() + fileExtension;

            Path targetLocation = this.fileStorageLocation.resolve(storedFilename);
            try (InputStream inputStream = file.getInputStream()) {
                Files.copy(inputStream, targetLocation, StandardCopyOption.REPLACE_EXISTING);
            }

            return storedFilename;
        } catch (IOException ex) {
            throw new FileStorageException("Could not store file " + cleanedFilename + ". Please try again!", ex);
        }
    }

    /**
     * 获取文件扩展名(内部方法)
     *
     * @param filename 文件名
     * @return 文件扩展名(包含点)，如".jpg"
     */
    private String getFileExtension(String filename) {
        if (filename == null || !filename.contains(".")) {
            return "";
        }
        return filename.substring(filename.lastIndexOf("."));
    }

    /**
     * {@inheritDoc}
     *
     * <p>实现细节：
     * <ol>
     *   <li>解析文件路径</li>
     *   <li>加载文件资源</li>
     *   <li>验证资源可读性</li>
     * </ol>
     *
     * <p>安全措施：
     * <ul>
     *   <li>规范化文件路径</li>
     *   <li>验证资源存在性和可读性</li>
     * </ul>
     */
    /**
     * {@inheritDoc}
     *
     * <p>实现细节：
     * <ol>
     *   <li>解析文件路径</li>
     *   <li>验证文件是否存在</li>
     *   <li>创建并返回文件资源</li>
     * </ol>
     */
    @Override
    public Resource loadFileAsResource(String fileName) {
        try {
            Path filePath = this.fileStorageLocation.resolve(fileName).normalize();
            Resource resource = new UrlResource(filePath.toUri());
            if (resource.exists() && resource.isReadable()) {
                return resource;
            } else {
                throw new FileStorageException("File not found or not readable: " + fileName);
            }
        } catch (MalformedURLException ex) {
            throw new FileStorageException("File not found: " + fileName, ex);
        }
    }
}