package sgc.portal.base.storage.web;

import sgc.portal.SgcPortalProperties;
import sgc.portal.base.storage.model.FileBucket;
import sgc.portal.base.storage.service.StorageService;
import sgc.portal.security.annotation.Secured;
import sgc.portal.security.annotation.SecurityPolicy;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.text.SimpleDateFormat;
import java.util.*;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 파일 다운로드 콘트롤러
 *
 * @author YongSang
 * @version 1.0.0
 * @since  2020. 9. 5.
 */
@Controller
@Secured(policy = SecurityPolicy.SESSION)
public class FileDownloadController {
    /**
     * The constant DEFAULT_ZIP_NAME.
     */
    private static final SimpleDateFormat DEFAULT_ZIP_NAME = new SimpleDateFormat("yyyyMMddHHmm", Locale.KOREA);

    @Autowired
    private StorageService service;

    @Autowired
    private SgcPortalProperties props;

    /**
     * 파일을 다운로드 한다 (단건)
     *
     * @param id 파일아이디
     * @return 파일 스트림
     * @throws IOException 파일 읽기에 실패할 경우
     */
    @GetMapping("/files/{id}/download")
    @Secured(policy = SecurityPolicy.NONE)
    public ResponseEntity<Resource> download(@PathVariable String id) throws IOException {
        return downloadInternal(service.load(id));
    }

    /**
     * 파일을 다운로드 한다 (여러건 중 한 개)
     *
     * @param id         파일아이디
     * @param backNumber 파일번호
     * @return 파일 스트림
     * @throws IOException 파일 읽기에 실패할 경우
     */
    @GetMapping({"/files/{id}/{backNumber}/download","/mobile/api/files/{id}/{backNumber}/download"})
    @Secured(policy = SecurityPolicy.NONE)
    public ResponseEntity<Resource> download(@PathVariable String id, @PathVariable Integer backNumber) throws IOException {
        return downloadInternal(service.load(id, backNumber));
    }

    /**
     * Img stream response entity.
     *
     * @param id         the id
     * @param backNumber the back number
     * @return the response entity
     * @throws IOException the io exception
     */
    @GetMapping("/files/{id}/{backNumber}/imgStream")
    @Secured(policy = SecurityPolicy.NONE)
    public @ResponseBody String imgStream(@PathVariable String id, @PathVariable Integer backNumber) throws IOException {
        String retVal = service.imgToHexInternal(id, backNumber);
        HttpHeaders headers = new HttpHeaders();
        return retVal;
    }


    /**
     * 파일을 압축하여 다운로드 한다 (여러건 중 다건)
     *
     * @param id 파일아이디
     * @param backNumberList 파일아이디
     * @param filename 다운로드 받을 압축 파일명. 파일명을 전달하지 않을경우 yyyyMMddHHmm.zip 형태로 압축 파일명 지정됨
     * @return 파일 스트림
     * @throws IOException        IOException 파일 읽기에 실패할 경우
     * @throws URISyntaxException 압축할 파일을 찾지 못 할 경우
     */
    @GetMapping("/files/{id}/{backNumberList}/download/partial")
    public ResponseEntity<Resource> download(@PathVariable String id, @PathVariable List<Integer> backNumberList, @RequestParam(required=false) String filename) throws IOException, URISyntaxException {
        return downloadInternal(service.load(id, backNumberList), StringUtils.isEmpty(filename) ? DEFAULT_ZIP_NAME.format(new Date()) : filename);
    }

    /**
     * 파일을 압축하여 다운로드 한다 (여러건)
     *
     * @param id       파일아이디
     * @param filename 다운로드 받을 압축 파일명. 파일명을 전달하지 않을경우 yyyyMMddHHmm.zip 형태로 압축 파일명 지정됨
     * @return 파일 스트림
     * @throws IOException        IOException 파일 읽기에 실패할 경우
     * @throws URISyntaxException 압축할 파일을 찾지 못 할 경우
     */
    @GetMapping("/files/{id}/download/all")
    public ResponseEntity<Resource> downloadAll(@PathVariable String id, @RequestParam(required=false) String filename) throws IOException, URISyntaxException {
        return downloadInternal(service.loadAll(id), StringUtils.isEmpty(filename) ? DEFAULT_ZIP_NAME.format(new Date()) : filename);
    }

    private ResponseEntity<Resource> downloadInternal(FileBucket fileBucket) throws IOException {
        if (fileBucket == null) {
            return null;
        }
        Path path = Paths.get(fileBucket.getLocation(), fileBucket.getChangedFilename());
        Resource resource = new ByteArrayResource(Files.readAllBytes(path));

        HttpHeaders headers = new HttpHeaders();
        headers.add("encoding", StandardCharsets.UTF_8.displayName());
        headers.setContentDispositionFormData("attachment", fileBucket.getOriginalFilename());
        return ResponseEntity.ok()
                .headers(headers)
                .contentLength(fileBucket.getFileSize())
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .body(resource);
    }

    private ResponseEntity<Resource> downloadInternal(List<FileBucket> fileBuckets, String filename) throws IOException {
        Path path = Paths.get(props.getStorage().getTempLocation(), RandomStringUtils.randomAlphanumeric(30) + ".zip");
        Files.createDirectories(path.getParent());

        Map<String, String> zipProps = new HashMap<>();
        zipProps.put("create", Boolean.toString(true));
        zipProps.put("encoding", StandardCharsets.UTF_8.displayName());
        URI zipFile = URI.create("jar:" + path.toUri());

        try (FileSystem zipfs = FileSystems.newFileSystem(zipFile, zipProps)) {
            for (FileBucket fileBucket : fileBuckets) {
                Path externalFile = Paths.get(fileBucket.getLocation(), fileBucket.getChangedFilename());
                Path pathInZipfile = zipfs.getPath(fileBucket.getOriginalFilename());
                Files.copy(externalFile, pathInZipfile, StandardCopyOption.REPLACE_EXISTING );
            }
        }

        if (filename.lastIndexOf(".zip") == -1) {
            filename = filename + ".zip";
        }
        Resource resource = new ByteArrayResource(Files.readAllBytes(path));
        HttpHeaders headers = new HttpHeaders();
        headers.setContentDispositionFormData("attachment", filename);

        return ResponseEntity.ok()
                .headers(headers)
                .contentLength(path.toFile().length())
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .body(resource);
    }
    
    @RequestMapping(value="/files/fileDownload")
    public void fileDownload(HttpServletResponse response, HttpServletRequest request, @RequestParam Map<String, String> paramMap) throws FileNotFoundException {

        String fileName = paramMap.get("fileName"); // 파일명 파라미터를 가져옵니다
        if (fileName == null || fileName.isEmpty()) {
            throw new IllegalArgumentException("파일명이 필요합니다.");
        }
        
        String path = "D:/runtime/files/direct/" + fileName; // 파일 경로를 동적으로 설정합니다
        File file = new File(path);
        
        if (!file.exists()) {
            throw new FileNotFoundException("파일이 존재하지 않습니다.");
        }

        FileInputStream fileInputStream = null;
        ServletOutputStream servletOutputStream = null;

        try {
            String downName = null;
            String browser = request.getHeader("User-Agent");
            // 파일 인코딩
            if (browser.contains("MSIE") || browser.contains("Trident") || browser.contains("Chrome")) { // 브라우저 확인 파일명 encode  
                downName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
            } else {
                downName = new String(fileName.getBytes("UTF-8"), "ISO-8859-1");
            }

            response.setHeader("Content-Disposition", "attachment;filename=\"" + downName + "\"");
            response.setContentType("application/octet-stream");
            response.setHeader("Content-Transfer-Encoding", "binary");

            fileInputStream = new FileInputStream(file);
            servletOutputStream = response.getOutputStream();

            byte[] b = new byte[1024];
            int data;

            while ((data = fileInputStream.read(b, 0, b.length)) != -1) {
                servletOutputStream.write(b, 0, data);
            }

            servletOutputStream.flush(); // 출력

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (servletOutputStream != null) {
                try {
                    servletOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}