package com.bio.bio_backend.domain.base.file.service;

import com.bio.bio_backend.domain.base.file.dto.FileUploadRequestDto;
import com.bio.bio_backend.domain.base.file.dto.FileUploadResponseDto;
import com.bio.bio_backend.domain.base.file.dto.MultipleFileUploadRequestDto;
import com.bio.bio_backend.domain.base.file.dto.MultipleFileUploadResponseDto;
import com.bio.bio_backend.domain.base.file.entity.File;
import com.bio.bio_backend.domain.base.file.repository.FileRepository;
import com.bio.bio_backend.global.exception.ApiException;
import com.bio.bio_backend.global.constants.ApiResponseCode;
import com.bio.bio_backend.global.utils.FileUtils;
import com.bio.bio_backend.global.utils.SecurityUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

import static com.bio.bio_backend.global.utils.OidUtils.generateOid;

@Service
@RequiredArgsConstructor
@Slf4j
@Transactional(readOnly = true)
public class FileServiceImpl implements FileService {

    private final FileRepository fileRepository;
    
    @Value("${app.file.upload.path}")
    private String uploadPath;
    
    @Value("${server.servlet.context-path}")
    private String contextPath;
    
    @Override
    @Transactional
    public FileUploadResponseDto uploadFile(FileUploadRequestDto requestDto) {
        MultipartFile multipartFile = requestDto.getFile();
        
        try {
            // 파일 유효성 검사
            FileUtils.validateFile(multipartFile);
        } catch (IllegalArgumentException e) {
            throw new ApiException(ApiResponseCode.FILE_EMPTY);
        }
        
        // 파일 업로드 처리
        File savedFile = processFileUpload(multipartFile, requestDto.getDescription(), generateOid());
        
        // 응답 DTO 생성 및 반환
        return FileUploadResponseDto.builder()
            .oid(savedFile.getOid())
            .groupOid(savedFile.getGroupOid())
            .originalFileName(savedFile.getOriginalFileName())
            .downloadUrl(contextPath + "/files/download/" + savedFile.getOid())
            .build();
    }
    
    @Override
    @Transactional
    public MultipleFileUploadResponseDto uploadMultipleFiles(MultipleFileUploadRequestDto requestDto) {
        List<MultipartFile> files = requestDto.getFiles();
        
        try {
            // 파일 리스트 유효성 검사
            FileUtils.validateFileList(files);
        } catch (IllegalArgumentException e) {
            throw new ApiException(ApiResponseCode.FILE_EMPTY);
        }
        
        List<FileUploadResponseDto> uploadedFiles = new ArrayList<>();
        List<String> errorMessages = new ArrayList<>();
        int successCount = 0;
        int failureCount = 0;

        Long groupOid = generateOid();
        for (MultipartFile multipartFile : files) {
            try {
                // 개별 파일 유효성 검사
                if (multipartFile.isEmpty()) {
                    String errorMsg = "파일 '" + multipartFile.getOriginalFilename() + "'이 비어있습니다.";
                    errorMessages.add(errorMsg);
                    failureCount++;
                    continue;
                }
                
                // 단일 파일 업로드 처리
                File savedFile = processFileUpload(multipartFile, requestDto.getDescription(), groupOid);
                FileUploadResponseDto uploadedFile = FileUploadResponseDto.builder()
                                .oid(savedFile.getOid())
                                .originalFileName(savedFile.getOriginalFileName())
                                .downloadUrl(contextPath + "/files/download/" + savedFile.getOid())
                                .build();
                
                uploadedFiles.add(uploadedFile);
                successCount++;
                
                log.info("파일 업로드 성공: {}", multipartFile.getOriginalFilename());
                
            } catch (Exception ex) {
                String fileName = multipartFile.getOriginalFilename() != null ? multipartFile.getOriginalFilename() : "알 수 없는 파일";
                log.error("파일 업로드 실패: {}", fileName, ex);
                errorMessages.add("파일 '" + fileName + "' 업로드 실패: " + ex.getMessage());
                failureCount++;
            }
        }
        
        return MultipleFileUploadResponseDto.builder()
                .groupOid(groupOid)
                .files(uploadedFiles)
                .totalCount(files.size())
                .successCount(successCount)
                .failureCount(failureCount)
                .errorMessages(errorMessages)
                .build();
    }
    
    /**
     * 파일 업로드 처리
     */
    private File processFileUpload(MultipartFile multipartFile, String description, Long groupOid) {
        String originalFileName = FileUtils.cleanFileName(multipartFile.getOriginalFilename());
        
        try {
            // 항상 년월일 기반으로 폴더 생성 (예: uploads/2024/01/15/)
            Path uploadDir = FileUtils.createYearMonthUploadDirectory(uploadPath);
            log.debug("년월 기반 폴더 사용: {}", uploadDir);
            
            // 파일명 및 확장자 처리
            String fileExtension = FileUtils.extractFileExtension(originalFileName);
            String storedFileName = FileUtils.generateUniqueFileName(fileExtension);
            
            // 파일 저장
            Path targetLocation = FileUtils.saveFileToDisk(multipartFile, uploadDir, storedFileName);
            
            // DB에 파일 정보 저장
            File file = createFileEntity(originalFileName, storedFileName, targetLocation, multipartFile, description, groupOid);
            
            return fileRepository.save(file);
            
        } catch (IOException ex) {
            log.error("파일 업로드 실패: {}", originalFileName, ex);
            throw new ApiException(ApiResponseCode.FILE_UPLOAD_FAILED, ex);
        }
    }

    private File createFileEntity(String originalFileName, String storedFileName, Path targetLocation, 
                                MultipartFile multipartFile, String description, Long groupOid) {
        return File.builder()
                .originalFileName(originalFileName)
                .storedFileName(storedFileName)
                .filePath(targetLocation.toString())
                .fileSize(multipartFile.getSize())
                .contentType(multipartFile.getContentType())
                .description(description)
                .groupOid(groupOid)
                .build();
    }
    
    @Override
    public File getFileByOid(Long oid) {
        return fileRepository.findByOidAndUseFlagTrue(oid)
                .orElseThrow(() -> new ApiException(ApiResponseCode.FILE_NOT_FOUND));
    }
    
    @Override
    public byte[] downloadFile(Long oid) {
        File file = fileRepository.findByOidAndUseFlagTrue(oid)
                .orElseThrow(() -> new ApiException(ApiResponseCode.FILE_NOT_FOUND));
        
        try {
            Path filePath = Paths.get(file.getFilePath());
            return Files.readAllBytes(filePath);
        } catch (IOException ex) {
            log.error("파일 다운로드 실패: {}", file.getOriginalFileName(), ex);
            throw new ApiException(ApiResponseCode.FILE_DOWNLOAD_FAILED, ex);
        }
    }
    
    @Override
    @Transactional
    public void deleteFile(Long oid) {
        File file = fileRepository.findByOidAndUseFlagTrue(oid)
                .orElseThrow(() -> new ApiException(ApiResponseCode.FILE_NOT_FOUND));

        String currentUserId = SecurityUtils.getCurrentUserId();
        // 현재 사용자가 파일 소유자인지 확인
        if (currentUserId == null || !currentUserId.equals(file.getCreatedId())) {
            throw new ApiException(ApiResponseCode.COMMON_FORBIDDEN);
        }
        
        // 논리적 삭제: use_flag를 false로 변경
        file.setUseFlag(false);
        fileRepository.save(file);
        
        log.info("파일 논리적 삭제 완료: oid={}, fileName={}", oid, file.getOriginalFileName());
    }
}
